[automerger skipped] Honor retry timer from setup data call response for emergency request am: 0129e3f5b2 -s ours

am skip reason: Merged-In I4aa32fc550dc47770e060abbe8f30156cc7d682e with SHA-1 0e4eed8a9c is already in history

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

Change-Id: I1116b8ebf83ef966a2de4b2da2188c2cba395fd8
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 4097571..c3b4373 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-
 package {
     // See: http://go/android-license-faq
     default_applicable_licenses: [
@@ -83,14 +82,14 @@
         "android.hardware.radio-V1.4-java",
         "android.hardware.radio-V1.5-java",
         "android.hardware.radio-V1.6-java",
-        "android.hardware.radio.config-V2-java",
-        "android.hardware.radio.data-V2-java",
-        "android.hardware.radio.ims-V1-java",
-        "android.hardware.radio.messaging-V2-java",
-        "android.hardware.radio.modem-V2-java",
-        "android.hardware.radio.network-V2-java",
-        "android.hardware.radio.sim-V2-java",
-        "android.hardware.radio.voice-V2-java",
+        "android.hardware.radio.config-V3-java",
+        "android.hardware.radio.data-V3-java",
+        "android.hardware.radio.ims-V2-java",
+        "android.hardware.radio.messaging-V3-java",
+        "android.hardware.radio.modem-V3-java",
+        "android.hardware.radio.network-V3-java",
+        "android.hardware.radio.sim-V3-java",
+        "android.hardware.radio.voice-V3-java",
         "voip-common",
         "ims-common",
         "unsupportedappusage",
@@ -100,23 +99,17 @@
         "android.hardware.radio.config-V1.1-java-shallow",
         "android.hardware.radio.config-V1.2-java-shallow",
         "android.hardware.radio.config-V1.3-java-shallow",
-        "android.hardware.radio.deprecated-V1.0-java-shallow",
         "ecc-protos-lite",
         "libphonenumber-nogeocoder",
         "PlatformProperties",
         "net-utils-framework-common",
         "telephony-protos",
         "modules-utils-build_system",
+        "modules-utils-fastxmlserializer",
         "modules-utils-statemachine",
+        "services-config-update",
     ],
 
-    product_variables: {
-        pdk: {
-            // enable this build only when platform library is available
-            enabled: false,
-        },
-    },
-
     optimize: {
         enabled: true,
         shrink: true,
diff --git a/OWNERS b/OWNERS
index a061cf0..ff1a04e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,8 +2,8 @@
 amallampati@google.com
 amruthr@google.com
 breadley@google.com
-chinmayd@google.com
 fionaxu@google.com
+grantmenke@google.com
 huiwang@google.com
 jackyu@google.com
 jayachandranc@google.com
@@ -17,6 +17,5 @@
 tnd@google.com
 xiaotonj@google.com
 
-
-
-
+# Domain Selection code is co-owned, adding additional owners for this code
+per-file EmergencyStateTracker*=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,jdyou@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 55f1ccc..59685b1 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -9,7 +9,8 @@
       ]
     },
     {
-      "name": "CarrierAppIntegrationTestCases"
+      "name": "CarrierAppIntegrationTestCases",
+      "keywords": ["internal"]
     },
     {
       "name": "CtsTelephony2TestCases",
diff --git a/flags/Android.bp b/flags/Android.bp
new file mode 100644
index 0000000..8f363b6
--- /dev/null
+++ b/flags/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2023 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+    name: "telephony_flags",
+    package: "com.android.internal.telephony.flags",
+    srcs: [
+        "calling.aconfig",
+        "data.aconfig",
+        "domainselection.aconfig",
+        "ims.aconfig",
+        "messaging.aconfig",
+        "misc.aconfig",
+        "network.aconfig",
+        "subscription.aconfig",
+        "uicc.aconfig",
+        "satellite.aconfig",
+        "iwlan.aconfig",
+        "telephony.aconfig",
+    ],
+}
diff --git a/flags/calling.aconfig b/flags/calling.aconfig
new file mode 100644
index 0000000..82f932b
--- /dev/null
+++ b/flags/calling.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+  name: "simultaneous_calling_indications"
+  namespace: "telephony"
+  description: "APIs that are used to notify simultaneous calling changes to other applications."
+  bug: "297446980"
+}
\ No newline at end of file
diff --git a/flags/data.aconfig b/flags/data.aconfig
new file mode 100644
index 0000000..cad7da7
--- /dev/null
+++ b/flags/data.aconfig
@@ -0,0 +1,113 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+  name: "auto_switch_allow_roaming"
+  namespace: "telephony"
+  description: "Allow using roaming network as target if user allows it from settings."
+  bug: "306488039"
+}
+
+flag {
+  name: "auto_data_switch_rat_ss"
+  namespace: "telephony"
+  description: "Whether switch for better rat and signal strength"
+  bug:"260928808"
+}
+
+flag {
+  name: "use_alarm_callback"
+  namespace: "telephony"
+  description: "Use alarm callback instead of broadcast."
+  bug: "311476875"
+}
+
+flag {
+  name: "refine_preferred_data_profile_selection"
+  namespace: "telephony"
+  description: "Upon internet network connect, refine selection of preferred data profile."
+  bug: "311476883"
+}
+
+flag {
+  name: "unthrottle_check_transport"
+  namespace: "telephony"
+  description: "Check transport when unthrottle."
+  bug: "303922311"
+}
+
+flag {
+  name: "relax_ho_teardown"
+  namespace: "telephony"
+  description: "Relax handover tear down if the device is currently in voice call."
+  bug: "270895912"
+}
+
+flag {
+  name: "allow_mmtel_in_non_vops"
+  namespace: "telephony"
+  description: "Allow bring up MMTEL in nonVops area specified by carrier config."
+  bug: "241198464"
+}
+
+flag {
+  name: "metered_embb_urlcc"
+  namespace: "telephony"
+  description: "Force networks that have PRIORITIZE_BANDWIDTH or PRIORITIZE_LATENCY to be metered."
+  bug: "301310451"
+  }
+
+flag {
+  name: "slicing_additional_error_codes"
+  namespace: "telephony"
+  description: "Support additional slicing error codes and functionality."
+  bug: "307378699"
+}
+
+flag {
+  name: "apn_setting_field_support_flag"
+  namespace: "telephony"
+  description: "Expose apn setting supporting field"
+  bug: "307038091"
+}
+
+flag {
+  name: "network_validation"
+  namespace: "telephony"
+  description: "Request network validation for data networks and response status."
+  bug:"286171724"
+}
+
+flag {
+ name: "notify_data_activity_changed_with_slot"
+  namespace: "telephony"
+  description: "notify data activity changed for slot id"
+  bug: "309896936"
+}
+
+flag {
+  name: "vonr_enabled_metric"
+  namespace: "telephony"
+  description: "Collect vonr status in voice call metric"
+  bug:"288449751"
+}
+
+flag {
+  name: "ignore_existing_networks_for_internet_allowed_checking"
+  namespace: "telephony"
+  description: "Ignore existing networks when checking if internet is allowed"
+  bug: "284420611"
+}
+
+flag {
+  name: "data_call_session_stats_captures_cross_sim_calling"
+  namespace: "telephony"
+  description: "The DataCallSessionStats metrics will capture whether the IWLAN PDN is set up on cross-SIM calling."
+  bug: "313956117"
+}
+
+flag {
+  name: "force_iwlan_mms"
+  namespace: "telephony"
+  description: "When QNS prefers MMS on IWLAN, MMS will be attempted on IWLAN if it can, even though if existing cellular network already supports MMS."
+  bug: "316211526"
+}
diff --git a/flags/domainselection.aconfig b/flags/domainselection.aconfig
new file mode 100644
index 0000000..2e1dfc8
--- /dev/null
+++ b/flags/domainselection.aconfig
@@ -0,0 +1,29 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+    name: "ap_domain_selection_enabled"
+    namespace: "telephony"
+    description: "This flag controls AP domain selection feature."
+    bug:"258112541"
+}
+
+flag {
+    name: "use_aosp_domain_selection_service"
+    namespace: "telephony"
+    description: "This flag controls AOSP's domain selection service supported."
+    bug:"258112541"
+}
+
+flag {
+    name: "use_oem_domain_selection_service"
+    namespace: "telephony"
+    description: "This flag controls OEMs' domain selection service supported."
+    bug:"258112541"
+}
+
+flag {
+    name: "domain_selection_metrics_enabled"
+    namespace: "telephony"
+    description: "This flag controls domain selection metrics."
+    bug:"258112541"
+}
diff --git a/flags/ims.aconfig b/flags/ims.aconfig
new file mode 100644
index 0000000..d09259e
--- /dev/null
+++ b/flags/ims.aconfig
@@ -0,0 +1,64 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+    name: "conference_hold_unhold_changed_to_send_message"
+    namespace: "telephony"
+    description: "This flag controls Conference’s hold & unHold operation changed to send a message"
+    bug:"288002989"
+}
+
+flag {
+    name: "ignore_already_terminated_incoming_call_before_registering_listener"
+    namespace: "telephony"
+    description: "This flag ignores the incoming call by throwing an exception if the call was already terminated before the framework registers the listener for the incoming call"
+    bug:"289461637"
+}
+
+flag {
+    name: "clear_cached_ims_phone_number_when_device_lost_ims_registration"
+    namespace: "telephony"
+    description: "This flag clears cached IMS phone number when device lost IMS registration"
+    bug:"288002989"
+}
+
+flag {
+    name: "update_ims_service_by_gathering_provisioning_changes"
+    namespace: "telephony"
+    description: "This flag is created to prevent unnecessary updates when multiple provisioning items to update ims service are changed."
+    bug:"302281114"
+}
+
+flag {
+    name: "add_rat_related_suggested_action_to_ims_registration"
+    namespace: "telephony"
+    description: "This flag is for adding suggested actions related to RAT to ims registration"
+    bug:"290573256"
+}
+
+flag {
+    name: "terminate_active_video_call_when_accepting_second_video_call_as_audio_only"
+    namespace: "telephony"
+    description: "This flag terminates active video call instead holding when accepting 2nd incoming video call as audio only"
+    bug:"309548300"
+}
+
+flag {
+    name: "emergency_registration_state"
+    namespace: "telephony"
+    description: "This flag is created to notify emergency registration state changed."
+    bug:"312101946"
+}
+
+flag {
+    name: "call_extra_for_non_hold_supported_carriers"
+    namespace: "telephony"
+    description: "For DSDA devices, controls whether the existing call will be dropped when an incoming call on a different sub is answered, when either sub does not support hold capability."
+    bug:"315993953"
+}
+
+flag {
+    name: "update_roaming_state_to_set_wfc_mode"
+    namespace: "telephony"
+    description: "This flag updates roaming state to set wfc mode"
+    bug:"317298331"
+}
diff --git a/flags/iwlan.aconfig b/flags/iwlan.aconfig
new file mode 100644
index 0000000..0dc9f8d
--- /dev/null
+++ b/flags/iwlan.aconfig
@@ -0,0 +1,14 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+    name: "enable_aead_algorithms"
+    namespace: "telephony"
+    description: "Add AEAD algorithms AES-GCM-8, AES-GCM-12 and AES-GCM-16 to IWLAN"
+    bug:"306119890"
+}
+flag {
+    name: "enable_multiple_sa_proposals"
+    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
new file mode 100644
index 0000000..1ba89ba
--- /dev/null
+++ b/flags/messaging.aconfig
@@ -0,0 +1,22 @@
+package: "com.android.internal.telephony.flags"
+
+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"
+}
+
+flag {
+  name: "sms_domain_selection_enabled"
+  namespace: "telephony"
+  description: "This flag controls AP domain selection support for normal/emergency SMS."
+  bug: "262804071"
+}
+
+flag {
+  name: "mms_disabled_error"
+  namespace: "telephony"
+  description: "This flag controls the support of the new MMS error code MMS_ERROR_MMS_DISABLED."
+  bug: "305062594"
+}
\ No newline at end of file
diff --git a/flags/misc.aconfig b/flags/misc.aconfig
new file mode 100644
index 0000000..dff6426
--- /dev/null
+++ b/flags/misc.aconfig
@@ -0,0 +1,72 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+  name: "do_not_override_precise_label"
+  namespace: "telephony"
+  description: "When set, Telecom will not override the precise label for certain disconnect causes."
+  bug: "296968778"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "log_mms_sms_database_access_info"
+  namespace: "telephony"
+  description: "Whether to log MMS/SMS database access info and report anomaly when getting exception."
+  bug: "275225402"
+}
+
+flag {
+  name: "stop_spamming_emergency_notification"
+  namespace: "telephony"
+  description: "When set, the no wifi emergency calling availability notif will have a do not ask again button"
+  bug: "275225402"
+}
+
+flag {
+  name: "enable_wps_check_api_flag"
+  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"
+}
+
+flag {
+  name: "ensure_access_to_call_settings_is_restricted"
+  namespace: "telephony"
+  description: "Check if access to mobile network configs restricted before displaying call options"
+  bug: "309655251"
+}
+
+flag {
+  name: "reorganize_roaming_notification"
+  namespace: "telephony"
+  description: "Reorganize conditions to show and dismiss roaming notifications."
+  bug: "310594087"
+}
+
+flag {
+  name: "dismiss_network_selection_notification_on_sim_disable"
+  namespace: "telephony"
+  description: "Fix to dismiss network selection notification when disable sim."
+  bug: "310594186"
+}
+
+flag {
+  name: "enable_telephony_analytics"
+  namespace: "telephony"
+  description: "Enable Telephony Analytics information of Service State , Sms and Call scenarios"
+  bug: "309896524"
+}
+
+flag {
+  name: "show_call_id_and_call_waiting_in_additional_settings_menu"
+  namespace: "telephony"
+  description: "Expose carrier config KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL and KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL."
+  bug: "310264981"
+}
+
+flag {
+    name: "reset_mobile_network_settings"
+    namespace: "telephony"
+    description: "Allows applications to launch Reset Mobile Network Settings page in Settings app."
+    bug:"271921464"
+}
diff --git a/flags/network.aconfig b/flags/network.aconfig
new file mode 100644
index 0000000..c0394e8
--- /dev/null
+++ b/flags/network.aconfig
@@ -0,0 +1,64 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+    name: "enable_carrier_config_n1_control"
+    namespace: "telephony"
+    description: "enabling this flag allows KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY to control N1 mode enablement"
+    bug:"302033535"
+}
+
+flag {
+  name: "hide_roaming_icon"
+  namespace: "telephony"
+  description: "Allow carriers to hide the roaming (R) icon when roaming."
+  bug: "301467052"
+}
+
+flag {
+  name: "enable_identifier_disclosure_transparency"
+  namespace: "telephony"
+  description: "Guards APIs for enabling and disabling identifier disclosure transparency"
+  bug: "276752426"
+}
+
+flag {
+  name: "enable_identifier_disclosure_transparency_unsol_events"
+  namespace: "telephony"
+  description: "Allows the framework to register for CellularIdentifierDisclosure events and emit notifications to the user about them"
+  bug: "276752426"
+}
+
+flag {
+  name: "enable_modem_cipher_transparency"
+  namespace: "telephony"
+  description: "Guards APIs for enabling and disabling modem cipher transparency."
+  bug: "283336425"
+}
+
+flag {
+  name: "enable_modem_cipher_transparency_unsol_events"
+  namespace: "telephony"
+  description: "Allows the framework to register for SecurityAlgorithmChanged events and emit notifications to the user when a device is using null ciphers."
+  bug: "283336425"
+}
+
+flag {
+  name: "hide_prefer_3g_item"
+  namespace: "telephony"
+  description: "Used in the Preferred Network Types menu to determine if the 3G option is displayed."
+  bug: "310639009"
+}
+
+flag {
+  name: "support_nr_sa_rrc_idle"
+  namespace: "telephony"
+  description: "Support RRC idle for NR SA."
+  bug: "298233308"
+}
+
+flag {
+  name: "network_registration_info_reject_cause"
+  namespace: "telephony"
+  description: "Elevate NRI#getRejectCause from System to Public"
+  bug: "239730435"
+}
diff --git a/flags/satellite.aconfig b/flags/satellite.aconfig
new file mode 100644
index 0000000..e640e6e
--- /dev/null
+++ b/flags/satellite.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+    name: "oem_enabled_satellite_flag"
+    namespace: "telephony"
+    description: "This flag controls satellite communication supported by OEMs."
+    bug:"291811962"
+}
+
+flag {
+    name: "carrier_enabled_satellite_flag"
+    namespace: "telephony"
+    description: "This flag controls satellite communication supported by carriers."
+    bug:"296437388"
+}
\ No newline at end of file
diff --git a/flags/subscription.aconfig b/flags/subscription.aconfig
new file mode 100644
index 0000000..cebedd5
--- /dev/null
+++ b/flags/subscription.aconfig
@@ -0,0 +1,43 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+  name: "work_profile_api_split"
+  namespace: "telephony"
+  description: "To support separation between personal and work from TelephonyManager and SubscriptionManager API perspective."
+  bug: "296076674"
+}
+
+flag {
+  name: "enforce_subscription_user_filter"
+  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"
+}
+
+flag {
+  name: "data_only_cellular_service"
+  namespace: "telephony"
+  description: "Supports customized cellular service capabilities per subscription."
+  bug: "296097429"
+}
+
+flag {
+  name: "data_only_service_allow_emergency_call_only"
+  namespace: "telephony"
+  description: "Support emergency call only for data only cellular service."
+  bug: "296097429"
+}
+
+flag {
+  name: "support_psim_to_esim_conversion"
+  namespace: "telephony"
+  description: "Support the psim to esim conversion."
+  bug: "315073761"
+}
+
+flag {
+  name: "subscription_user_association_query"
+  namespace: "telephony"
+  description: "Supports querying if a subscription is associated with the caller"
+  bug: "325045841"
+}
\ No newline at end of file
diff --git a/flags/telephony.aconfig b/flags/telephony.aconfig
new file mode 100644
index 0000000..d59b249
--- /dev/null
+++ b/flags/telephony.aconfig
@@ -0,0 +1,29 @@
+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"
+}
\ No newline at end of file
diff --git a/flags/uicc.aconfig b/flags/uicc.aconfig
new file mode 100644
index 0000000..c1b860f
--- /dev/null
+++ b/flags/uicc.aconfig
@@ -0,0 +1,32 @@
+package: "com.android.internal.telephony.flags"
+
+flag {
+    name: "esim_bootstrap_provisioning_flag"
+    namespace: "telephony"
+    description: "This flag controls eSIM Bootstrap provisioning feature support."
+    bug:"298567545"
+}
+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"
+}
+flag {
+    name: "carrier_restriction_status"
+    namespace: "telephony"
+    description: "This flag controls the visibility of the getCarrierRestrictionStatus in carrierRestrictionRules class."
+    bug:"313553044"
+}
+flag {
+    name: "carrier_restriction_rules_enhancement"
+    namespace: "telephony"
+    description: "This flag controls the new enhancements to the existing carrier restrictions rules"
+    bug:"317226653"
+}
+flag {
+    name: "esim_available_memory"
+    namespace: "telephony"
+    description: "This flag controls eSIM available memory feature."
+    bug:"318348580"
+}
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 42e8fa4..c07c797 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -190,6 +190,12 @@
     /* Number of time the user toggled the data switch feature since the last collection. */
     optional int32 auto_data_switch_toggle_count = 55;
 
+    /* Consolidated emergency numbers list information. */
+    repeated EmergencyNumbersInfo emergency_numbers_info = 56;
+
+    /* Timestamp of last emergency number pull. */
+    optional int64 emergency_number_pull_timestamp_millis = 57;
+
     /** Snapshot of satellite controller. */
     repeated SatelliteController satellite_controller = 58;
 
@@ -225,12 +231,6 @@
 
     /* Timestamp of last satellite_sos_message_recommender pull. */
     optional int64 satellite_sos_message_recommender_pull_timestamp_millis = 69;
-
-    /* Consolidated emergency numbers list information. */
-    repeated EmergencyNumbersInfo emergency_numbers_info = 56;
-
-    /* Timestamp of last emergency number pull. */
-    optional int64 emergency_number_pull_timestamp_millis = 57;
 }
 
 // The canonical versions of the following enums live in:
@@ -280,6 +280,7 @@
     optional bool is_iwlan_cross_sim_at_start = 37;
     optional bool is_iwlan_cross_sim_at_end = 38;
     optional bool is_iwlan_cross_sim_at_connected = 39;
+    optional bool vonr_enabled = 40;
 
     // Internal use only
     optional int64 setup_begin_millis = 10001;
@@ -420,6 +421,7 @@
 }
 
 message ImsRegistrationStats {
+    reserved 16;
     optional int32 carrier_id = 1;
     optional int32 sim_slot_index = 2;
     optional int32 rat = 3;
@@ -436,6 +438,7 @@
     optional int64 registering_millis = 13;
     optional int64 unregistered_millis = 14;
     optional bool is_iwlan_cross_sim = 15;
+    optional int32 registered_times = 17;
 
     // Internal use only
     optional int64 last_used_millis = 10001;
@@ -597,6 +600,41 @@
     optional int32 short_code_sms_count = 3;
 }
 
+message EmergencyNumbersInfo {
+    enum ServiceCategory {
+        EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED = 0;
+        EMERGENCY_SERVICE_CATEGORY_POLICE = 1;
+        EMERGENCY_SERVICE_CATEGORY_AMBULANCE = 2;
+        EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE = 3;
+        EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD = 4;
+        EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE = 5;
+        EMERGENCY_SERVICE_CATEGORY_MIEC = 6;
+        EMERGENCY_SERVICE_CATEGORY_AIEC = 7;
+    }
+    enum Source {
+        EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING = 0;
+        EMERGENCY_NUMBER_SOURCE_SIM = 1;
+        EMERGENCY_NUMBER_SOURCE_DATABASE = 2;
+        EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG = 3;
+        EMERGENCY_NUMBER_SOURCE_DEFAULT = 4;
+    }
+    enum CallRoute {
+        EMERGENCY_CALL_ROUTE_UNKNOWN = 0;
+        EMERGENCY_CALL_ROUTE_EMERGENCY = 1;
+        EMERGENCY_CALL_ROUTE_NORMAL = 2;
+    }
+    optional bool is_db_version_ignored = 1;
+    optional int32 asset_version = 2;
+    optional int32 ota_version = 3;
+    optional string number = 4;
+    optional string country_iso = 5;
+    optional string mnc = 6;
+    optional CallRoute route = 7;
+    repeated string urns = 8;
+    repeated ServiceCategory service_categories = 9;
+    repeated Source sources = 10;
+}
+
 message SatelliteController {
     optional int32 count_of_satellite_service_enablements_success = 1;
     optional int32 count_of_satellite_service_enablements_fail = 2;
@@ -649,39 +687,7 @@
     optional bool is_ims_registered = 3;
     optional int32 cellular_service_state = 4;
     optional int32 count = 5;
-}
-
-message EmergencyNumbersInfo {
-    enum ServiceCategory {
-        EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED = 0;
-        EMERGENCY_SERVICE_CATEGORY_POLICE = 1;
-        EMERGENCY_SERVICE_CATEGORY_AMBULANCE = 2;
-        EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE = 3;
-        EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD = 4;
-        EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE = 5;
-        EMERGENCY_SERVICE_CATEGORY_MIEC = 6;
-        EMERGENCY_SERVICE_CATEGORY_AIEC = 7;
-    }
-    enum Source {
-        EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING = 0;
-        EMERGENCY_NUMBER_SOURCE_SIM = 1;
-        EMERGENCY_NUMBER_SOURCE_DATABASE = 2;
-        EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG = 3;
-        EMERGENCY_NUMBER_SOURCE_DEFAULT = 4;
-    }
-    enum CallRoute {
-        EMERGENCY_CALL_ROUTE_UNKNOWN = 0;
-        EMERGENCY_CALL_ROUTE_EMERGENCY = 1;
-        EMERGENCY_CALL_ROUTE_NORMAL = 2;
-    }
-    optional bool is_db_version_ignored = 1;
-    optional int32 asset_version = 2;
-    optional int32 ota_version = 3;
-    optional string number = 4;
-    optional string country_iso = 5;
-    optional string mnc = 6;
-    optional CallRoute route = 7;
-    repeated string urns = 8;
-    repeated ServiceCategory service_categories = 9;
-    repeated Source sources = 10;
+    optional bool is_multi_sim = 6;
+    optional int32 recommending_handover_type = 7;
+    optional bool is_satellite_allowed_in_current_location = 8;
 }
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index b8de975..6f66545 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -18,7 +18,6 @@
 package com.android.internal.telephony;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
@@ -69,8 +68,6 @@
     protected RegistrantList mVoicePrivacyOnRegistrants = new RegistrantList();
     protected RegistrantList mVoicePrivacyOffRegistrants = new RegistrantList();
     @UnsupportedAppUsage
-    protected Registrant mUnsolOemHookRawRegistrant;
-    @UnsupportedAppUsage
     protected RegistrantList mOtaProvisionRegistrants = new RegistrantList();
     @UnsupportedAppUsage
     protected RegistrantList mCallWaitingInfoRegistrants = new RegistrantList();
@@ -121,13 +118,9 @@
     protected RegistrantList mConnectionSetupFailureRegistrants = new RegistrantList();
     protected RegistrantList mNotifyAnbrRegistrants = new RegistrantList();
     protected RegistrantList mTriggerImsDeregistrationRegistrants = new RegistrantList();
-    protected RegistrantList mPendingSatelliteMessageCountRegistrants = new RegistrantList();
-    protected RegistrantList mNewSatelliteMessagesRegistrants = new RegistrantList();
-    protected RegistrantList mSatelliteMessagesTransferCompleteRegistrants = new RegistrantList();
-    protected RegistrantList mSatellitePointingInfoChangedRegistrants = new RegistrantList();
-    protected RegistrantList mSatelliteModeChangedRegistrants = new RegistrantList();
-    protected RegistrantList mSatelliteRadioTechnologyChangedRegistrants = new RegistrantList();
-    protected RegistrantList mSatelliteProvisionStateChangedRegistrants = new RegistrantList();
+    protected RegistrantList mImeiInfoRegistrants = new RegistrantList();
+    protected RegistrantList mCellularIdentifierDisclosedRegistrants = new RegistrantList();
+    protected RegistrantList mSecurityAlgorithmUpdatedRegistrants = new RegistrantList();
 
     @UnsupportedAppUsage
     protected Registrant mGsmSmsRegistrant;
@@ -681,17 +674,6 @@
         mSignalInfoRegistrants.addUnique(h, what, obj);
     }
 
-    public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
-        mUnsolOemHookRawRegistrant = new Registrant (h, what, obj);
-    }
-
-    public void unSetOnUnsolOemHookRaw(Handler h) {
-        if (mUnsolOemHookRawRegistrant != null && mUnsolOemHookRawRegistrant.getHandler() == h) {
-            mUnsolOemHookRawRegistrant.clear();
-            mUnsolOemHookRawRegistrant = null;
-        }
-    }
-
     @Override
     public void unregisterForSignalInfo(Handler h) {
         mSignalInfoRegistrants.remove(h);
@@ -1011,18 +993,6 @@
     }
 
     @Override
-    public void startLceService(int reportIntervalMs, boolean pullMode, Message result) {
-    }
-
-    @Override
-    public void stopLceService(Message result) {
-    }
-
-    @Override
-    public void pullLceData(Message result) {
-    }
-
-    @Override
     public void registerForLceInfo(Handler h, int what, Object obj) {
         synchronized (mStateMonitor) {
             mLceInfoRegistrants.addUnique(h, what, obj);
@@ -1208,80 +1178,31 @@
         mTriggerImsDeregistrationRegistrants.remove(h);
     }
 
+    /**
+     * Register to listen for the changes in the primary IMEI with respect to the sim slot.
+     */
     @Override
-    public void registerForPendingSatelliteMessageCount(
-            @NonNull Handler h, int what, @Nullable Object obj) {
-        mPendingSatelliteMessageCountRegistrants.add(h, what, obj);
+    public void registerForImeiMappingChanged(Handler h, int what, Object obj) {
+        mImeiInfoRegistrants.add(h, what, obj);
     }
 
     @Override
-    public void unregisterForPendingSatelliteMessageCount(@NonNull Handler h) {
-        mPendingSatelliteMessageCountRegistrants.remove(h);
+    public void registerForCellularIdentifierDisclosures(Handler h, int what, Object obj) {
+        mCellularIdentifierDisclosedRegistrants.add(h, what, obj);
     }
 
     @Override
-    public void registerForNewSatelliteMessages(
-            @NonNull Handler h, int what, @Nullable Object obj) {
-        mNewSatelliteMessagesRegistrants.add(h, what, obj);
+    public void unregisterForCellularIdentifierDisclosures(Handler h) {
+        mCellularIdentifierDisclosedRegistrants.remove(h);
     }
 
     @Override
-    public void unregisterForNewSatelliteMessages(@NonNull Handler h) {
-        mNewSatelliteMessagesRegistrants.remove(h);
+    public void registerForSecurityAlgorithmUpdates(Handler h, int what, Object obj) {
+        mSecurityAlgorithmUpdatedRegistrants.add(h, what, obj);
     }
 
     @Override
-    public void registerForSatelliteMessagesTransferComplete(@NonNull Handler h,
-            int what, @Nullable Object obj) {
-        mSatelliteMessagesTransferCompleteRegistrants.add(h, what, obj);
-    }
-
-    @Override
-    public void unregisterForSatelliteMessagesTransferComplete(@NonNull Handler h) {
-        mSatelliteMessagesTransferCompleteRegistrants.remove(h);
-    }
-
-    @Override
-    public void registerForSatellitePointingInfoChanged(@NonNull Handler h,
-            int what, @Nullable Object obj) {
-        mSatellitePointingInfoChangedRegistrants.add(h, what, obj);
-    }
-
-    @Override
-    public void unregisterForSatellitePointingInfoChanged(@NonNull Handler h) {
-        mSatellitePointingInfoChangedRegistrants.remove(h);
-    }
-
-    @Override
-    public void registerForSatelliteModeChanged(@NonNull Handler h,
-            int what, @Nullable Object obj) {
-        mSatelliteModeChangedRegistrants.add(h, what, obj);
-    }
-
-    @Override
-    public void unregisterForSatelliteModeChanged(@NonNull Handler h) {
-        mSatelliteModeChangedRegistrants.remove(h);
-    }
-
-    @Override
-    public void registerForSatelliteRadioTechnologyChanged(@NonNull Handler h,
-            int what, @Nullable Object obj) {
-        mSatelliteRadioTechnologyChangedRegistrants.add(h, what, obj);
-    }
-
-    @Override
-    public void unregisterForSatelliteRadioTechnologyChanged(@NonNull Handler h) {
-        mSatelliteRadioTechnologyChangedRegistrants.remove(h);
-    }
-
-    @Override
-    public void registerForSatelliteProvisionStateChanged(@NonNull Handler h,
-            int what, @Nullable Object obj) {
-        mSatelliteProvisionStateChangedRegistrants.add(h, what, obj);
-    }
-
-    @Override
-    public void unregisterForSatelliteProvisionStateChanged(@NonNull Handler h) {
-        mSatelliteProvisionStateChangedRegistrants.remove(h);
+    public void unregisterForSecurityAlgorithmUpdates(Handler h) {
+        mSecurityAlgorithmUpdatedRegistrants.remove(h);
     }
 }
diff --git a/src/java/com/android/internal/telephony/CallTracker.java b/src/java/com/android/internal/telephony/CallTracker.java
index 38c6672..5e617f9 100644
--- a/src/java/com/android/internal/telephony/CallTracker.java
+++ b/src/java/com/android/internal/telephony/CallTracker.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
@@ -25,8 +26,11 @@
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.flags.FeatureFlags;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -55,6 +59,9 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected boolean mNumberConverted = false;
+
+    protected final @NonNull FeatureFlags mFeatureFlags;
+
     private final int VALID_COMPARE_LENGTH   = 3;
 
     //***** Events
@@ -77,7 +84,8 @@
     protected static final int EVENT_THREE_WAY_DIAL_BLANK_FLASH    = 20;
 
     @UnsupportedAppUsage
-    public CallTracker() {
+    public CallTracker(FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
     }
 
     protected void pollCallsWhenSafe() {
@@ -91,6 +99,14 @@
 
     protected void
     pollCallsAfterDelay() {
+        if (mFeatureFlags.preventInvocationRepeatOfRilCallWhenDeviceDoesNotSupportVoice()) {
+            if (!mCi.getHalVersion(TelephonyManager.HAL_SERVICE_VOICE)
+                    .greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
+                log("Skip polling because HAL_SERVICE_VOICE < RADIO_HAL_VERSION_1.4");
+                return;
+            }
+        }
+
         Message msg = obtainMessage();
 
         msg.what = EVENT_REPOLL_AFTER_DELAY;
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index beb6b26..9143f21 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -20,6 +20,7 @@
 
 import android.app.AlarmManager;
 import android.app.DownloadManager;
+import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -30,6 +31,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.UserManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.SubscriptionManager;
@@ -39,6 +41,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -107,36 +110,58 @@
     private String mURL;
     private boolean mAllowedOverMeteredNetwork = false;
     private boolean mDeleteOldKeyAfterDownload = false;
+    private boolean mIsRequiredToHandleUnlock;
     private TelephonyManager mTelephonyManager;
+    private UserManager mUserManager;
 
     @VisibleForTesting
     public String mMccMncForDownload;
     public int mCarrierId;
     @VisibleForTesting
     public long mDownloadId;
+    private final FeatureFlags mFeatureFlags;
 
-    public CarrierKeyDownloadManager(Phone phone) {
+    public CarrierKeyDownloadManager(Phone phone, FeatureFlags featureFlags) {
         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);
         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);
         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) -> {
-                    if (slotIndex == mPhone.getPhoneId()) {
+                    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));
                     }
                 });
     }
 
+    private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                Log.d(LOG_TAG, "Received UserUnlockedReceiver");
+                handleAlarmOrConfigChange();
+            }
+        }
+    };
+
     private final BroadcastReceiver mDownloadReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -167,6 +192,16 @@
                     Log.d(LOG_TAG, "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);
+                }
             }
         }
     };
@@ -205,6 +240,16 @@
                 // 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;
+                        }
+                    }
                     resetRenewalAlarm();
                 }
             } else {
@@ -214,6 +259,7 @@
             // delete any existing alarms.
             cleanupRenewalAlarms();
             mPhone.deleteCarrierInfoForImsiEncryption(getSimCarrierId());
+
         }
     }
 
@@ -603,7 +649,8 @@
             mCarrierId = carrierId;
             mDownloadId = carrierKeyDownloadRequestId;
         } catch (Exception e) {
-            Log.e(LOG_TAG, "exception trying to download key from url: " + mURL);
+            Log.e(LOG_TAG, "exception trying to download key from url: " + mURL + ", Exception = "
+                    + e.getMessage());
             return false;
         }
         return true;
diff --git a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
index ab7ebc4..67be1b6 100644
--- a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
@@ -560,7 +560,7 @@
     }
 
     private void updateCertsForPackage(@NonNull PackageInfo pkg) {
-        Set<String> certs = new ArraySet<>();
+        Set<String> certs = new ArraySet<>(1);
         List<Signature> signatures = UiccAccessRule.getSignatures(pkg);
         for (Signature signature : signatures) {
             byte[] sha1 = UiccAccessRule.getCertHash(signature, SHA_1);
@@ -773,7 +773,7 @@
             return mCachedUids.get(pkgName);
         }
 
-        Set<Integer> uids = new ArraySet<>();
+        Set<Integer> uids = new ArraySet<>(1);
         List<UserInfo> users = mUserManager.getUsers();
         for (UserInfo user : users) {
             int userId = user.getUserHandle().getIdentifier();
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index 93f5ab0..6b99b56 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -16,16 +16,22 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.RadioAccessFamily;
@@ -37,6 +43,7 @@
 import android.telephony.TelephonyManager.NetworkTypeBitMask;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.telephony.Rlog;
@@ -66,6 +73,11 @@
     public static final int NOTIFICATION_PREF_NETWORK = 1000;
     public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001;
 
+
+    @VisibleForTesting
+    public static final String ACTION_NEVER_ASK_AGAIN = "SilenceNoWifiEmrgCallingNotification";
+    public final NotificationActionReceiver mActionReceiver = new NotificationActionReceiver();
+
     @VisibleForTesting
     public static final String EMERGENCY_NOTIFICATION_TAG = "EmergencyNetworkNotification";
 
@@ -75,6 +87,7 @@
     private long mAllowedNetworkType = -1;
     private AllowedNetworkTypesListener mAllowedNetworkTypesListener;
     private TelephonyManager mTelephonyManager;
+    @NonNull private final FeatureFlags mFeatureFlags;
 
     /**
      * The listener for allowed network types changed
@@ -95,7 +108,9 @@
         }
     }
 
-    public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) {
+    public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst,
+            @NonNull FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
         this.mPhone = phone;
         this.mSST = sst;
         mTelephonyManager = mPhone.getContext().getSystemService(
@@ -144,12 +159,24 @@
                     }
                 });
 
-        registerNotificationTypes();
+        if (!mPhone.getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WATCH)) {
+            registerNotificationTypes();
+        }
+
         mAllowedNetworkType = RadioAccessFamily.getNetworkTypeFromRaf(
                 (int) mPhone.getAllowedNetworkTypes(
                         TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
         mAllowedNetworkTypesListener = new AllowedNetworkTypesListener();
         registerAllowedNetworkTypesListener();
+
+        if (mFeatureFlags.stopSpammingEmergencyNotification()) {
+            // register a receiver for notification actions
+            mPhone.getContext().registerReceiver(
+                    mActionReceiver,
+                    new IntentFilter(ACTION_NEVER_ASK_AGAIN),
+                    Context.RECEIVER_NOT_EXPORTED);
+        }
     }
 
     /**
@@ -376,11 +403,18 @@
      */
     @VisibleForTesting
     public void sendNotification(NotificationType notificationType) {
+        Context context = mPhone.getContext();
+
         if (!evaluateSendingMessage(notificationType)) {
             return;
         }
 
-        Context context = mPhone.getContext();
+        if (mFeatureFlags.stopSpammingEmergencyNotification()
+                && shouldSilenceEmrgNetNotif(notificationType, context)) {
+            Rlog.i(LOG_TAG, "sendNotification: silencing NOTIFICATION_EMERGENCY_NETWORK");
+            return;
+        }
+
         Notification.Builder builder = getNotificationBuilder(notificationType);
         // set some common attributes
         builder.setWhen(System.currentTimeMillis())
@@ -394,6 +428,15 @@
     }
 
     /**
+     * This helper checks if the user has set a flag to silence the notification permanently
+     */
+    private boolean shouldSilenceEmrgNetNotif(NotificationType notificationType, Context context) {
+        return notificationType.getTypeId() == NOTIFICATION_EMERGENCY_NETWORK
+                && PreferenceManager.getDefaultSharedPreferences(context)
+                .getBoolean(ACTION_NEVER_ASK_AGAIN, false);
+    }
+
+    /**
      * Cancel notifications if a registration is pending or has been sent.
      **/
     public void cancelNotification(NotificationType notificationType) {
@@ -646,12 +689,57 @@
                     com.android.internal.R.string.EmergencyCallWarningTitle);
             CharSequence details = res.getText(
                     com.android.internal.R.string.EmergencyCallWarningSummary);
-            return new Notification.Builder(context)
-                    .setContentTitle(title)
-                    .setStyle(new Notification.BigTextStyle().bigText(details))
-                    .setContentText(details)
-                    .setOngoing(true)
-                    .setChannelId(NotificationChannelController.CHANNEL_ID_WFC);
+            if (mFeatureFlags.stopSpammingEmergencyNotification()) {
+                return new Notification.Builder(context)
+                        .setContentTitle(title)
+                        .setStyle(new Notification.BigTextStyle().bigText(details))
+                        .setContentText(details)
+                        .setOngoing(true)
+                        .setActions(createDoNotShowAgainAction(context))
+                        .setChannelId(NotificationChannelController.CHANNEL_ID_WFC);
+            } else {
+                return new Notification.Builder(context)
+                        .setContentTitle(title)
+                        .setStyle(new Notification.BigTextStyle().bigText(details))
+                        .setContentText(details)
+                        .setOngoing(true)
+                        .setChannelId(NotificationChannelController.CHANNEL_ID_WFC);
+            }
+        }
+
+        /**
+         * add a button to the notification that has a broadcast intent embedded to silence the
+         * notification
+         */
+        private Notification.Action createDoNotShowAgainAction(Context context) {
+            final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                    context,
+                    0,
+                    new Intent(ACTION_NEVER_ASK_AGAIN),
+                    PendingIntent.FLAG_IMMUTABLE);
+            return new Notification.Action.Builder(null, "Do Not Show Again",
+                    pendingIntent).build();
+        }
+    }
+
+    /**
+     * This receiver listens to notification actions and can be utilized to do things like silence
+     * a notification that is spammy.
+     */
+    public class NotificationActionReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(ACTION_NEVER_ASK_AGAIN)) {
+                Rlog.i(LOG_TAG, "NotificationActionReceiver: ACTION_NEVER_ASK_AGAIN");
+                // insert a key to silence future notifications
+                SharedPreferences.Editor editor =
+                        PreferenceManager.getDefaultSharedPreferences(context).edit();
+                editor.putBoolean(ACTION_NEVER_ASK_AGAIN, true);
+                editor.apply();
+                // Note: If another action is added, unregistering here should be removed. However,
+                // since there is no longer a reason to broadcasts, cleanup mActionReceiver.
+                context.unregisterReceiver(mActionReceiver);
+            }
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
index 33cde4f..e187989 100644
--- a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
+++ b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
@@ -58,7 +58,7 @@
     public static final int EVENT_ON_FILTER_COMPLETE_NOT_CALLED = 1;
 
     /** onFilterComplete timeout. */
-    public static final int FILTER_COMPLETE_TIMEOUT_MS = 10 * 60 * 1000; //10 minutes
+    public static final int FILTER_COMPLETE_TIMEOUT_MS = 12 * 60 * 1000; //12 minutes
 
     /** SMS anomaly uuid -- CarrierMessagingService did not respond */
     private static final UUID sAnomalyNoResponseFromCarrierMessagingService =
@@ -381,7 +381,9 @@
         }
 
         private void addToCallbacks(CarrierSmsFilterCallback callback) {
-            mCallbacks.add(callback);
+            synchronized (mFilterLock) {
+                mCallbacks.add(callback);
+            }
         }
 
     }
diff --git a/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java
index 7e8663a..adb9904 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java
@@ -54,6 +54,8 @@
     private static final int EVENT_ACTIVATION_DONE = 3;
     private static final int EVENT_RADIO_OFF = 4;
     private static final int EVENT_SUBSCRIPTION_CHANGED = 5;
+    @VisibleForTesting
+    public static final int EVENT_RADIO_RESET = 6;
 
     private static final int SMS_CB_CODE_SCHEME_MIN = 0;
     private static final int SMS_CB_CODE_SCHEME_MAX = 255;
@@ -122,6 +124,7 @@
         @Override
         public void enter() {
             mPhone.registerForRadioOffOrNotAvailable(getHandler(), EVENT_RADIO_OFF, null);
+            mPhone.mCi.registerForModemReset(getHandler(), EVENT_RADIO_RESET, null);
             mPhone.getContext().getSystemService(SubscriptionManager.class)
                     .addOnSubscriptionsChangedListener(new HandlerExecutor(getHandler()),
                             mSubChangedListener);
@@ -130,6 +133,7 @@
         @Override
         public void exit() {
             mPhone.unregisterForRadioOffOrNotAvailable(getHandler());
+            mPhone.mCi.unregisterForModemReset(getHandler());
             mPhone.getContext().getSystemService(SubscriptionManager.class)
                     .removeOnSubscriptionsChangedListener(mSubChangedListener);
         }
@@ -142,6 +146,7 @@
             }
             switch (msg.what) {
                 case EVENT_RADIO_OFF:
+                case EVENT_RADIO_RESET:
                     resetConfig();
                     break;
                 case EVENT_SUBSCRIPTION_CHANGED:
diff --git a/src/java/com/android/internal/telephony/CellularNetworkService.java b/src/java/com/android/internal/telephony/CellularNetworkService.java
index 9cbd7a6..bff1d41 100644
--- a/src/java/com/android/internal/telephony/CellularNetworkService.java
+++ b/src/java/com/android/internal/telephony/CellularNetworkService.java
@@ -25,9 +25,11 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AnomalyReporter;
+import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityCdma;
 import android.telephony.CellIdentityGsm;
@@ -42,6 +44,7 @@
 import android.telephony.NetworkServiceCallback;
 import android.telephony.NrVopsSupportInfo;
 import android.telephony.ServiceState;
+import android.telephony.SmsManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.VopsSupportInfo;
@@ -230,6 +233,10 @@
                     || regState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
                 if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
                     availableServices.add(NetworkRegistrationInfo.SERVICE_TYPE_DATA);
+                    if (isMmsEnabled(mPhone)) {
+                        // Add SERVICE_TYPE_MMS only if MMS is enabled
+                        availableServices.add(NetworkRegistrationInfo.SERVICE_TYPE_MMS);
+                    }
                 } else if (domain == NetworkRegistrationInfo.DOMAIN_CS) {
                     availableServices.add(NetworkRegistrationInfo.SERVICE_TYPE_VOICE);
                     availableServices.add(NetworkRegistrationInfo.SERVICE_TYPE_SMS);
@@ -279,27 +286,6 @@
             } else if (result instanceof android.hardware.radio.V1_5.RegStateResult) {
                 return getNetworkRegistrationInfo(domain, transportType,
                         (android.hardware.radio.V1_5.RegStateResult) result);
-            } else if (result instanceof android.hardware.radio.V1_0.VoiceRegStateResult) {
-                android.hardware.radio.V1_0.VoiceRegStateResult voiceRegState =
-                        (android.hardware.radio.V1_0.VoiceRegStateResult) result;
-                int regState = getRegStateFromHalRegState(voiceRegState.regState);
-                int networkType = ServiceState.rilRadioTechnologyToNetworkType(voiceRegState.rat);
-                int reasonForDenial = voiceRegState.reasonForDenial;
-                boolean emergencyOnly = isEmergencyOnly(voiceRegState.regState);
-                boolean cssSupported = voiceRegState.cssSupported;
-                int roamingIndicator = voiceRegState.roamingIndicator;
-                int systemIsInPrl = voiceRegState.systemIsInPrl;
-                int defaultRoamingIndicator = voiceRegState.defaultRoamingIndicator;
-                List<Integer> availableServices = getAvailableServices(
-                        regState, domain, emergencyOnly);
-                CellIdentity cellIdentity =
-                        RILUtils.convertHalCellIdentity(voiceRegState.cellIdentity);
-                final String rplmn = getPlmnFromCellIdentity(cellIdentity);
-
-                return new NetworkRegistrationInfo(domain, transportType, regState,
-                        networkType, reasonForDenial, emergencyOnly, availableServices,
-                        cellIdentity, rplmn, cssSupported, roamingIndicator, systemIsInPrl,
-                        defaultRoamingIndicator);
             } else if (result instanceof android.hardware.radio.V1_2.VoiceRegStateResult) {
                 android.hardware.radio.V1_2.VoiceRegStateResult voiceRegState =
                         (android.hardware.radio.V1_2.VoiceRegStateResult) result;
@@ -330,20 +316,6 @@
             final int domain = NetworkRegistrationInfo.DOMAIN_PS;
             final int transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
 
-            int regState = NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
-            int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
-            int reasonForDenial = 0;
-            boolean emergencyOnly = false;
-            int maxDataCalls = 0;
-            CellIdentity cellIdentity;
-            boolean isEndcAvailable = false;
-            boolean isNrAvailable = false;
-            boolean isDcNrRestricted = false;
-
-            LteVopsSupportInfo lteVopsSupportInfo =
-                    new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
-                            LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
-
             if (result instanceof android.hardware.radio.network.RegStateResult) {
                 return getNetworkRegistrationInfoAidl(domain, transportType,
                         (android.hardware.radio.network.RegStateResult) result);
@@ -353,66 +325,46 @@
             } else if (result instanceof android.hardware.radio.V1_5.RegStateResult) {
                 return getNetworkRegistrationInfo(domain, transportType,
                         (android.hardware.radio.V1_5.RegStateResult) result);
-            } else if (result instanceof android.hardware.radio.V1_0.DataRegStateResult) {
-                android.hardware.radio.V1_0.DataRegStateResult dataRegState =
-                        (android.hardware.radio.V1_0.DataRegStateResult) result;
-                regState = getRegStateFromHalRegState(dataRegState.regState);
-                networkType = ServiceState.rilRadioTechnologyToNetworkType(dataRegState.rat);
-                reasonForDenial = dataRegState.reasonDataDenied;
-                emergencyOnly = isEmergencyOnly(dataRegState.regState);
-                maxDataCalls = dataRegState.maxDataCalls;
-                cellIdentity = RILUtils.convertHalCellIdentity(dataRegState.cellIdentity);
-            } else if (result instanceof android.hardware.radio.V1_2.DataRegStateResult) {
-                android.hardware.radio.V1_2.DataRegStateResult dataRegState =
-                        (android.hardware.radio.V1_2.DataRegStateResult) result;
-                regState = getRegStateFromHalRegState(dataRegState.regState);
-                networkType = ServiceState.rilRadioTechnologyToNetworkType(dataRegState.rat);
-                reasonForDenial = dataRegState.reasonDataDenied;
-                emergencyOnly = isEmergencyOnly(dataRegState.regState);
-                maxDataCalls = dataRegState.maxDataCalls;
-                cellIdentity = RILUtils.convertHalCellIdentity(dataRegState.cellIdentity);
             } else if (result instanceof android.hardware.radio.V1_4.DataRegStateResult) {
                 android.hardware.radio.V1_4.DataRegStateResult dataRegState =
                         (android.hardware.radio.V1_4.DataRegStateResult) result;
-                regState = getRegStateFromHalRegState(dataRegState.base.regState);
-                networkType = ServiceState.rilRadioTechnologyToNetworkType(dataRegState.base.rat);
-
-                reasonForDenial = dataRegState.base.reasonDataDenied;
-                emergencyOnly = isEmergencyOnly(dataRegState.base.regState);
-                maxDataCalls = dataRegState.base.maxDataCalls;
-                cellIdentity = RILUtils.convertHalCellIdentity(dataRegState.base.cellIdentity);
-                android.hardware.radio.V1_4.NrIndicators nrIndicators = dataRegState.nrIndicators;
-
+                LteVopsSupportInfo lteVopsSupportInfo;
                 // Check for lteVopsInfo only if its initialized and RAT is EUTRAN
                 if (dataRegState.vopsInfo.getDiscriminator() == hidl_discriminator.lteVopsInfo
                         && ServiceState.rilRadioTechnologyToAccessNetworkType(dataRegState.base.rat)
                             == AccessNetworkType.EUTRAN) {
                     android.hardware.radio.V1_4.LteVopsInfo vopsSupport =
                             dataRegState.vopsInfo.lteVopsInfo();
-                    lteVopsSupportInfo = convertHalLteVopsSupportInfo(vopsSupport.isVopsSupported,
-                        vopsSupport.isEmcBearerSupported);
+                    lteVopsSupportInfo = convertHalLteVopsSupportInfo(
+                            vopsSupport.isVopsSupported, vopsSupport.isEmcBearerSupported);
                 } else {
-                    lteVopsSupportInfo =
-                        new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
-                        LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
+                    lteVopsSupportInfo = new LteVopsSupportInfo(
+                            LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
+                            LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
                 }
-
-                isEndcAvailable = nrIndicators.isEndcAvailable;
-                isNrAvailable = nrIndicators.isNrAvailable;
-                isDcNrRestricted = nrIndicators.isDcNrRestricted;
+                int regState = getRegStateFromHalRegState(dataRegState.base.regState);
+                int networkType =
+                        ServiceState.rilRadioTechnologyToNetworkType(dataRegState.base.rat);
+                int reasonForDenial = dataRegState.base.reasonDataDenied;
+                boolean emergencyOnly = isEmergencyOnly(dataRegState.base.regState);
+                int maxDataCalls = dataRegState.base.maxDataCalls;
+                CellIdentity cellIdentity =
+                        RILUtils.convertHalCellIdentity(dataRegState.base.cellIdentity);
+                android.hardware.radio.V1_4.NrIndicators nrIndicators = dataRegState.nrIndicators;
+                boolean isEndcAvailable = nrIndicators.isEndcAvailable;
+                boolean isNrAvailable = nrIndicators.isNrAvailable;
+                boolean isDcNrRestricted = nrIndicators.isDcNrRestricted;
+                String rplmn = getPlmnFromCellIdentity(cellIdentity);
+                List<Integer> availableServices = getAvailableServices(
+                        regState, domain, emergencyOnly);
+                return new NetworkRegistrationInfo(domain, transportType, regState, networkType,
+                        reasonForDenial, emergencyOnly, availableServices, cellIdentity, rplmn,
+                        maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable,
+                        lteVopsSupportInfo);
             } else {
                 loge("Unknown type of DataRegStateResult " + result);
                 return null;
             }
-
-            String rplmn = getPlmnFromCellIdentity(cellIdentity);
-            List<Integer> availableServices = getAvailableServices(
-                    regState, domain, emergencyOnly);
-
-            return new NetworkRegistrationInfo(domain, transportType, regState, networkType,
-                    reasonForDenial, emergencyOnly, availableServices, cellIdentity, rplmn,
-                    maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable,
-                    lteVopsSupportInfo);
         }
 
         private @NonNull NetworkRegistrationInfo getNetworkRegistrationInfo(
@@ -805,6 +757,23 @@
         return new CellularNetworkServiceProvider(slotIndex);
     }
 
+    private boolean isMmsEnabled(Phone phone) {
+        CarrierConfigManager carrierConfigManager = phone.getContext()
+                .getSystemService(CarrierConfigManager.class);
+        if (carrierConfigManager != null) {
+            PersistableBundle config = carrierConfigManager.getConfigForSubId(
+                    phone.getSubId(), SmsManager.MMS_CONFIG_MMS_ENABLED);
+            if (config == null || config.isEmpty()) {
+                config = CarrierConfigManager.getDefaultConfig();
+            }
+
+            return config.getBoolean(SmsManager.MMS_CONFIG_MMS_ENABLED);
+        } else {
+            loge("isMmsEnabled: CarrierConfigManager is null");
+            return false;
+        }
+    }
+
     private static void log(String s) {
         Rlog.d(TAG, s);
     }
diff --git a/src/java/com/android/internal/telephony/CommandException.java b/src/java/com/android/internal/telephony/CommandException.java
index e068c1c..6b80e9f 100644
--- a/src/java/com/android/internal/telephony/CommandException.java
+++ b/src/java/com/android/internal/telephony/CommandException.java
@@ -131,6 +131,17 @@
         BLOCKED_DUE_TO_CALL,
         RF_HARDWARE_ISSUE,
         NO_RF_CALIBRATION_INFO,
+        ENCODING_NOT_SUPPORTED,
+        FEATURE_NOT_SUPPORTED,
+        INVALID_CONTACT,
+        MODEM_INCOMPATIBLE,
+        NETWORK_TIMEOUT,
+        NO_SATELLITE_SIGNAL,
+        NOT_SUFFICIENT_ACCOUNT_BALANCE,
+        RADIO_TECHNOLOGY_NOT_SUPPORTED,
+        SUBSCRIBER_NOT_AUTHORIZED,
+        SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL,
+        UNIDENTIFIED_SUBSCRIBER
     }
 
     @UnsupportedAppUsage
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index 971e051..ee7447c 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -74,7 +74,6 @@
     // Used as parameters for call forward methods below
     static final int CF_ACTION_DISABLE          = 0;
     static final int CF_ACTION_ENABLE           = 1;
-//  static final int CF_ACTION_UNUSED           = 2;
     static final int CF_ACTION_REGISTRATION     = 3;
     static final int CF_ACTION_ERASURE          = 4;
 
@@ -628,85 +627,85 @@
     @UnsupportedAppUsage
     void setEmergencyCallbackMode(Handler h, int what, Object obj);
 
-     /**
-      * Fires on any CDMA OTA provision status change
-      */
-     @UnsupportedAppUsage
-     void registerForCdmaOtaProvision(Handler h,int what, Object obj);
-     @UnsupportedAppUsage
-     void unregisterForCdmaOtaProvision(Handler h);
+    /**
+     * Fires on any CDMA OTA provision status change
+     */
+    @UnsupportedAppUsage
+    void registerForCdmaOtaProvision(Handler h, int what, Object obj);
+    @UnsupportedAppUsage
+    void unregisterForCdmaOtaProvision(Handler h);
 
-     /**
-      * Registers the handler when out-band ringback tone is needed.<p>
-      *
-      *  Messages received from this:
-      *  Message.obj will be an AsyncResult
-      *  AsyncResult.userObj = obj
-      *  AsyncResult.result = boolean. <p>
-      */
-     void registerForRingbackTone(Handler h, int what, Object obj);
-     void unregisterForRingbackTone(Handler h);
+    /**
+     * Registers the handler when out-band ringback tone is needed.<p>
+     *
+     *  Messages received from this:
+     *  Message.obj will be an AsyncResult
+     *  AsyncResult.userObj = obj
+     *  AsyncResult.result = boolean. <p>
+     */
+    void registerForRingbackTone(Handler h, int what, Object obj);
+    void unregisterForRingbackTone(Handler h);
 
-     /**
-      * Registers the handler when mute/unmute need to be resent to get
-      * uplink audio during a call.<p>
-      *
-      * @param h Handler for notification message.
-      * @param what User-defined message code.
-      * @param obj User object.
-      *
-      */
-     void registerForResendIncallMute(Handler h, int what, Object obj);
-     void unregisterForResendIncallMute(Handler h);
+    /**
+     * Registers the handler when mute/unmute need to be resent to get
+     * uplink audio during a call.<p>
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     *
+     */
+    void registerForResendIncallMute(Handler h, int what, Object obj);
+    void unregisterForResendIncallMute(Handler h);
 
-     /**
-      * Registers the handler for when Cdma subscription changed events
-      *
-      * @param h Handler for notification message.
-      * @param what User-defined message code.
-      * @param obj User object.
-      *
-      */
-     void registerForCdmaSubscriptionChanged(Handler h, int what, Object obj);
-     void unregisterForCdmaSubscriptionChanged(Handler h);
+    /**
+     * Registers the handler for when Cdma subscription changed events
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     *
+     */
+    void registerForCdmaSubscriptionChanged(Handler h, int what, Object obj);
+    void unregisterForCdmaSubscriptionChanged(Handler h);
 
-     /**
-      * Registers the handler for when Cdma prl changed events
-      *
-      * @param h Handler for notification message.
-      * @param what User-defined message code.
-      * @param obj User object.
-      *
-      */
-     void registerForCdmaPrlChanged(Handler h, int what, Object obj);
-     void unregisterForCdmaPrlChanged(Handler h);
+    /**
+     * Registers the handler for when Cdma prl changed events
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     *
+     */
+    void registerForCdmaPrlChanged(Handler h, int what, Object obj);
+    void unregisterForCdmaPrlChanged(Handler h);
 
-     /**
-      * Registers the handler for when Cdma prl changed events
-      *
-      * @param h Handler for notification message.
-      * @param what User-defined message code.
-      * @param obj User object.
-      *
-      */
-     void registerForExitEmergencyCallbackMode(Handler h, int what, Object obj);
-     void unregisterForExitEmergencyCallbackMode(Handler h);
+    /**
+     * Registers the handler for when Cdma prl changed events
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     *
+     */
+    void registerForExitEmergencyCallbackMode(Handler h, int what, Object obj);
+    void unregisterForExitEmergencyCallbackMode(Handler h);
 
-     /**
-      * Registers the handler for RIL_UNSOL_RIL_CONNECT events.
-      *
-      * When ril connects or disconnects a message is sent to the registrant
-      * which contains an AsyncResult, ar, in msg.obj. The ar.result is an
-      * Integer which is the version of the ril or -1 if the ril disconnected.
-      *
-      * @param h Handler for notification message.
-      * @param what User-defined message code.
-      * @param obj User object.
-      */
-     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-     void registerForRilConnected(Handler h, int what, Object obj);
-     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-     void unregisterForRilConnected(Handler h);
+    /**
+     * Registers the handler for RIL_UNSOL_RIL_CONNECT events.
+     *
+     * When ril connects or disconnects a message is sent to the registrant
+     * which contains an AsyncResult, ar, in msg.obj. The ar.result is an
+     * Integer which is the version of the ril or -1 if the ril disconnected.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    void registerForRilConnected(Handler h, int what, Object obj);
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    void unregisterForRilConnected(Handler h);
 
     /**
      * Registers the handler for RIL_UNSOL_SIM_DETACH_FROM_NETWORK_CONFIG_CHANGED events.
@@ -905,18 +904,6 @@
      *  ar.exception carries exception on failure
      *  ar.userObject contains the orignal value of result.obj
      *  ar.result contains a List of DataCallResponse
-     *  @deprecated Do not use.
-     */
-    @UnsupportedAppUsage
-    @Deprecated
-    void getPDPContextList(Message result);
-
-    /**
-     *  returned message
-     *  retMsg.obj = AsyncResult ar
-     *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
-     *  ar.result contains a List of DataCallResponse
      */
     @UnsupportedAppUsage
     void getDataCallList(Message result);
@@ -933,7 +920,7 @@
      * CLIR_INVOCATION  == on "CLIR invocation" (restrict CLI presentation)
      */
     void dial(String address, boolean isEmergencyCall, EmergencyNumber emergencyNumberInfo,
-              boolean hasKnownUserIntentEmergency, int clirMode, Message result);
+            boolean hasKnownUserIntentEmergency, int clirMode, Message result);
 
     /**
      *  returned message
@@ -947,7 +934,7 @@
      * CLIR_INVOCATION  == on "CLIR invocation" (restrict CLI presentation)
      */
     void dial(String address, boolean isEmergencyCall, EmergencyNumber emergencyNumberInfo,
-              boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result);
+            boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result);
 
     /**
      *  returned message
@@ -969,25 +956,6 @@
     void getIMSIForApp(String aid, Message result);
 
     /**
-     *  returned message
-     *  retMsg.obj = AsyncResult ar
-     *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
-     *  ar.result is String containing IMEI on success
-     */
-    void getIMEI(Message result);
-
-    /**
-     *  returned message
-     *  retMsg.obj = AsyncResult ar
-     *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
-     *  ar.result is String containing IMEISV on success
-     */
-    @UnsupportedAppUsage
-    void getIMEISV(Message result);
-
-    /**
      * Hang up one individual connection.
      *  returned message
      *  retMsg.obj = AsyncResult ar
@@ -1102,25 +1070,6 @@
      */
     void getLastCallFailCause (Message result);
 
-
-    /**
-     * Reason for last PDP context deactivate or failure to activate
-     * cause code returned as int[0] in Message.obj.response
-     * returns an integer cause code defined in TS 24.008
-     * section 6.1.3.1.3 or close approximation
-     * @deprecated Do not use.
-     */
-    @UnsupportedAppUsage
-    @Deprecated
-    void getLastPdpFailCause (Message result);
-
-    /**
-     * The preferred new alternative to getLastPdpFailCause
-     * that is also CDMA-compatible.
-     */
-    @UnsupportedAppUsage
-    void getLastDataCallFailCause (Message result);
-
     void setMute (boolean enableMute, Message response);
 
     void getMute (Message response);
@@ -1422,7 +1371,7 @@
      */
     @UnsupportedAppUsage
     void setCallForward(int action, int cfReason, int serviceClass,
-                String number, int timeSeconds, Message response);
+            String number, int timeSeconds, Message response);
 
     /**
      * cfReason is one of CF_REASON_*
@@ -1500,7 +1449,7 @@
 
     @UnsupportedAppUsage
     void queryFacilityLock (String facility, String password, int serviceClass,
-        Message response);
+            Message response);
 
     /**
      * (AsyncResult)response.obj).result will be an Integer representing
@@ -1515,7 +1464,7 @@
      */
 
     void queryFacilityLockForApp(String facility, String password, int serviceClass, String appId,
-        Message response);
+            Message response);
 
     /**
      * @param facility one of CB_FACILTY_*
@@ -1526,7 +1475,7 @@
      */
     @UnsupportedAppUsage
     void setFacilityLock (String facility, boolean lockState, String password,
-        int serviceClass, Message response);
+            int serviceClass, Message response);
 
     /**
      * Set the facility lock for the app with this AID on the ICC card.
@@ -1539,7 +1488,7 @@
      * @param response is callback message
      */
     void setFacilityLockForApp(String facility, boolean lockState, String password,
-        int serviceClass, String appId, Message response);
+            int serviceClass, String appId, Message response);
 
     void sendUSSD (String ussdString, Message response);
 
@@ -1549,8 +1498,6 @@
      */
     void cancelPendingUssd (Message response);
 
-    void resetRadio(Message result);
-
     /**
      * Assign a specified band for RF configuration.
      *
@@ -1578,7 +1525,7 @@
     @UnsupportedAppUsage
     void setPreferredNetworkType(int networkType , Message response);
 
-     /**
+    /**
      *  Query the preferred network type setting
      *
      * @param response is callback message to report one of  NT_*_TYPE
@@ -1595,7 +1542,7 @@
     void setAllowedNetworkTypesBitmap(
             @TelephonyManager.NetworkTypeBitMask int networkTypeBitmask, Message response);
 
-     /**
+    /**
      *  Query the allowed network types setting.
      *
      * @param response is callback message to report allowed network types bitmask
@@ -1681,28 +1628,14 @@
     @UnsupportedAppUsage
     void reportStkServiceIsRunning(Message result);
 
-    @UnsupportedAppUsage
-    void invokeOemRilRequestRaw(byte[] data, Message response);
-
     /**
      * Sends carrier specific information to the vendor ril that can be used to
      * encrypt the IMSI and IMPI.
      *
-     * @param publicKey the public key of the carrier used to encrypt IMSI/IMPI.
-     * @param keyIdentifier the key identifier is optional information that is carrier
-     *        specific.
+     * @param imsiEncryptionInfo the IMSI encryption info
      * @param response callback message
      */
-    void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
-                                         Message response);
-
-    void invokeOemRilRequestStrings(String[] strings, Message response);
-
-    /**
-     * Fires when RIL_UNSOL_OEM_HOOK_RAW is received from the RIL.
-     */
-    void setOnUnsolOemHookRaw(Handler h, int what, Object obj);
-    void unSetOnUnsolOemHookRaw(Handler h);
+    void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo, Message response);
 
     /**
      * Send TERMINAL RESPONSE to the SIM, after processing a proactive command
@@ -1802,6 +1735,12 @@
     public void getImei(Message response);
 
     /**
+     * Register to listen for the changes in the primary IMEI with respect to the sim slot.
+     */
+
+    public void registerForImeiMappingChanged(Handler h, int what, Object obj);
+
+    /**
      * Request the device MDN / H_SID / H_NID / MIN.
      * "response" is const char **
      *   [0] is MDN if CDMA subscription is available
@@ -1879,15 +1818,13 @@
     void queryTTYMode(Message response);
 
     /**
-     * Setup a packet data connection On successful completion, the result
+     * Setup a packet data connection. On successful completion, the result
      * message will return a SetupDataResult object containing the connection information.
      *
      * @param accessNetworkType
      *            Access network to use. Values is one of AccessNetworkConstants.AccessNetworkType.
      * @param dataProfile
      *            Data profile for data call setup
-     * @param isRoaming
-     *            Device is roaming or not
      * @param allowRoaming
      *            Flag indicating data roaming is enabled or not
      * @param reason
@@ -1918,9 +1855,9 @@
      * @param result
      *            Callback message
      */
-    void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
-            boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
-            NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
+    void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean allowRoaming,
+            int reason, LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
+            TrafficDescriptor trafficDescriptor,
             boolean matchAllRuleAllowed, Message result);
 
     /**
@@ -1982,22 +1919,6 @@
     public void getIccCardStatus(Message result);
 
     /**
-     * Request the status of all the physical UICC slots.
-     *
-     * @param result Callback message containing a {@link java.util.ArrayList} of
-     * {@link com.android.internal.telephony.uicc.IccSlotStatus} instances for all the slots.
-     */
-    void getIccSlotsStatus(Message result);
-
-    /**
-     * Set the mapping from logical slots to physical slots.
-     *
-     * @param physicalSlots Mapping from logical slots to physical slots.
-     * @param result Callback message is empty on completion.
-     */
-    void setLogicalToPhysicalSlotMapping(int[] physicalSlots, Message result);
-
-    /**
      * Request the SIM application on the UICC to perform authentication
      * challenge/response algorithm. The data string and challenge response are
      * Base64 encoded Strings.
@@ -2074,24 +1995,20 @@
      *
      * @param dataProfile
      *            data profile for initial APN attach
-     * @param isRoaming
-     *            indicating the device is roaming or not
      * @param result
      *            callback message contains the information of SUCCESS/FAILURE
      */
-    void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, Message result);
+    void setInitialAttachApn(DataProfile dataProfile, Message result);
 
     /**
      * Set data profiles in modem
      *
      * @param dps
      *            Array of the data profiles set to modem
-     * @param isRoaming
-     *            Indicating if the device is roaming or not
      * @param result
      *            callback message contains the information of SUCCESS/FAILURE
      */
-    void setDataProfile(DataProfile[] dps, boolean isRoaming, Message result);
+    void setDataProfile(DataProfile[] dps, Message result);
 
     /**
      * Notifiy that we are testing an emergency call
@@ -2236,7 +2153,7 @@
         return HalVersion.UNKNOWN;
     }
 
-   /**
+    /**
      * Sets user selected subscription at Modem.
      *
      * @param slotId
@@ -2256,15 +2173,6 @@
             Message result);
 
     /**
-     * Whether the device modem supports reporting the EID in either the slot or card status or
-     * through ATR.
-     * @return true if the modem supports EID.
-     */
-    default boolean supportsEid() {
-        return false;
-    }
-
-    /**
      * Tells the modem if data is allowed or not.
      *
      * @param allowed
@@ -2318,34 +2226,6 @@
     public void unregisterForRadioCapabilityChanged(Handler h);
 
     /**
-     * Start LCE (Link Capacity Estimation) service with a desired reporting interval.
-     *
-     * @param reportIntervalMs
-     *        LCE info reporting interval (ms).
-     *
-     * @param result Callback message contains the current LCE status.
-     * {byte status, int actualIntervalMs}
-     */
-    public void startLceService(int reportIntervalMs, boolean pullMode, Message result);
-
-    /**
-     * Stop LCE service.
-     *
-     * @param result Callback message contains the current LCE status:
-     * {byte status, int actualIntervalMs}
-     *
-     */
-    public void stopLceService(Message result);
-
-    /**
-     * Pull LCE service for capacity data.
-     *
-     * @param result Callback message contains the capacity info:
-     * {int capacityKbps, byte confidenceLevel, byte lceSuspendedTemporarily}
-     */
-    public void pullLceData(Message result);
-
-    /**
      * Register a LCE info listener.
      *
      * @param h Handler for notification message.
@@ -2738,7 +2618,7 @@
         return true;
     };
 
-   /**
+    /**
      * Return the class name of the currently bound modem service.
      *
      * @return the class name of the modem service.
@@ -2747,18 +2627,18 @@
         return "default";
     };
 
-   /**
+    /**
      * Request the SIM phonebook records of all activated UICC applications
      *
      * @param result Callback message containing the count of ADN valid record.
      */
-    public void getSimPhonebookRecords(Message result);
+    void getSimPhonebookRecords(Message result);
 
-   /**
+    /**
      * Request the SIM phonebook Capacity of all activated UICC applications
      *
      */
-    public void getSimPhonebookCapacity(Message result);
+    void getSimPhonebookCapacity(Message result);
 
     /**
      * Request to insert/delete/update the SIM phonebook record
@@ -2766,7 +2646,7 @@
      * @param phonebookRecordInfo adn record information to be updated
      * @param result Callback message containing the SIM phonebook record index.
      */
-    public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecordInfo, Message result);
+    void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecordInfo, Message result);
 
     /**
      * Registers the handler when the SIM phonebook is changed.
@@ -2775,14 +2655,14 @@
      * @param what User-defined message code.
      * @param obj User object .
      */
-    public void registerForSimPhonebookChanged(Handler h, int what, Object obj);
+    void registerForSimPhonebookChanged(Handler h, int what, Object obj);
 
     /**
      * Unregister for notifications when SIM phonebook has already init done.
      *
      * @param h Handler to be removed from the registrant list.
      */
-    public void unregisterForSimPhonebookChanged(Handler h);
+    void unregisterForSimPhonebookChanged(Handler h);
 
     /**
      * Registers the handler when a group of SIM phonebook records received.
@@ -2791,14 +2671,14 @@
      * @param what User-defined message code.
      * @param obj User object.
      */
-    public void registerForSimPhonebookRecordsReceived(Handler h, int what, Object obj);
+    void registerForSimPhonebookRecordsReceived(Handler h, int what, Object obj);
 
     /**
      * Unregister for notifications when a group of SIM phonebook records received.
      *
      * @param h Handler to be removed from the registrant list.
      */
-     public void unregisterForSimPhonebookRecordsReceived(Handler h);
+    void unregisterForSimPhonebookRecordsReceived(Handler h);
 
     /**
      * Registers for notifications of connection setup failure.
@@ -3019,261 +2899,57 @@
     default void isN1ModeEnabled(Message result) {}
 
     /**
-     * Get feature capabilities supported by satellite.
+     * Enables or disables cellular identifier disclosure transparency.
      *
-     * @param result Message that will be sent back to the requester
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @param result Callback message to receive the result.
      */
-    default void getSatelliteCapabilities(Message result) {}
+    default void setCellularIdentifierTransparencyEnabled(boolean enable, Message result) {}
 
     /**
-     * Turn satellite modem on/off.
+     * Check whether cellular identifier transparency.
      *
-     * @param result Message that will be sent back to the requester
-     * @param on {@code true} for turning on.
-     *           {@code false} for turning off.
+     * @param result Callback message to receive the result.
      */
-    default void setSatellitePower(Message result, boolean on) {}
+    default void isCellularIdentifierTransparencyEnabled(Message result) {}
 
     /**
-     * Get satellite modem state.
+     * Enables or disables security algorithm update reports.
      *
-     * @param result Message that will be sent back to the requester
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @param result Callback message to receive the result.
      */
-    default void getSatellitePowerState(Message result) {}
+    default void setSecurityAlgorithmsUpdatedEnabled(boolean enable, Message result) {}
 
     /**
-     * Get satellite provision state.
+     * Check whether security algorithm update reports are enabled.
      *
-     * @param result Message that will be sent back to the requester
+     * @param result Callback message to receive the result.
      */
-    default void getSatelliteProvisionState(Message result) {}
+    default void isSecurityAlgorithmsUpdatedEnabled(Message result) {}
 
     /**
-     * Check whether satellite modem is supported by the device.
-     *
-     * @param result Message that will be sent back to the requester
+     * Registers for cellular identifier disclosure events.
      */
-    default void isSatelliteSupported(Message result) {}
+    default void registerForCellularIdentifierDisclosures(
+            @NonNull Handler h, int what, @Nullable Object obj) {}
 
     /**
-     * Provision the subscription with a satellite provider. This is needed to register the
-     * subscription if the provider allows dynamic registration.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param imei IMEI of the SIM associated with the satellite modem.
-     * @param msisdn MSISDN of the SIM associated with the satellite modem.
-     * @param imsi IMSI of the SIM associated with the satellite modem.
-     * @param features List of features to be provisioned.
-     */
-    default void provisionSatelliteService(
-            Message result, String imei, String msisdn, String imsi, int[] features) {}
-
-    /**
-     * Add contacts that are allowed to be used for satellite communication. This is applicable for
-     * incoming messages as well.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param contacts List of allowed contacts to be added.
-     */
-    default void addAllowedSatelliteContacts(Message result, String[] contacts) {}
-
-    /**
-     * Remove contacts that are allowed to be used for satellite communication. This is applicable
-     * for incoming messages as well.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param contacts List of allowed contacts to be removed.
-     */
-    default void removeAllowedSatelliteContacts(Message result, String[] contacts) {}
-
-    /**
-     * Send text messages.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param messages List of messages in text format to be sent.
-     * @param destination The recipient of the message.
-     * @param latitude The current latitude of the device.
-     * @param longitude The current longitude of the device. The location (i.e., latitude and
-     *        longitude) of the device will be filled for emergency messages.
-     */
-    default void sendSatelliteMessages(Message result, String[] messages, String destination,
-            double latitude, double longitude) {}
-
-    /**
-     * Get pending messages.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    default void getPendingSatelliteMessages(Message result) {}
-
-    /**
-     * Get current satellite registration mode.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    default void getSatelliteMode(Message result) {}
-
-    /**
-     * Set the filter for what type of indication framework want to receive from modem.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param filterBitmask The filter bitmask identifying what type of indication Telephony
-     *                      framework wants to receive from modem.
-     */
-    default void setSatelliteIndicationFilter(Message result, int filterBitmask) {}
-
-    /**
-     * User started pointing to the satellite. Modem should continue to update the ponting input
-     * as user moves device.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    default void startSendingSatellitePointingInfo(Message result) {}
-
-    /**
-     * Stop sending satellite pointing info to the framework.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    default void stopSendingSatellitePointingInfo(Message result) {}
-
-    /**
-     * Get max number of characters per text message.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    default void getMaxCharactersPerSatelliteTextMessage(Message result) {}
-
-    /**
-     * Get whether satellite communication is allowed for the current location.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    default void isSatelliteCommunicationAllowedForCurrentLocation(Message result) {}
-
-    /**
-     * Get the time after which the satellite will be visible.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    default void getTimeForNextSatelliteVisibility(Message result) {}
-
-    /**
-     * Registers for pending message count from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    default void registerForPendingSatelliteMessageCount(@NonNull Handler h,
-            int what, @Nullable Object obj) {}
-
-    /**
-     * Unregisters for pending message count from satellite modem.
+     * Unregisters for cellular identifier disclosure events.
      *
      * @param h Handler to be removed from the registrant list.
      */
-    default void unregisterForPendingSatelliteMessageCount(@NonNull Handler h) {}
+    default void unregisterForCellularIdentifierDisclosures(@NonNull Handler h) {}
 
     /**
-     * Registers for new messages from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
+     * Registers for security algorithm update events.
      */
-    default void registerForNewSatelliteMessages(@NonNull Handler h,
-            int what, @Nullable Object obj) {}
+    default void registerForSecurityAlgorithmUpdates(Handler h, int what, Object obj) {}
 
     /**
-     * Unregisters for new messages from satellite modem.
+     * Unregisters for security algorithm update events.
      *
      * @param h Handler to be removed from the registrant list.
      */
-    default void unregisterForNewSatelliteMessages(@NonNull Handler h) {}
-
-    /**
-     * Registers for messages transfer complete from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    default void registerForSatelliteMessagesTransferComplete(@NonNull Handler h,
-            int what, @Nullable Object obj) {}
-
-    /**
-     * Unregisters for messages transfer complete from satellite modem.
-     *
-     * @param h Handler to be removed from the registrant list.
-     */
-    default void unregisterForSatelliteMessagesTransferComplete(@NonNull Handler h) {}
-
-    /**
-     * Registers for pointing info changed from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    default void registerForSatellitePointingInfoChanged(@NonNull Handler h,
-            int what, @Nullable Object obj) {}
-
-    /**
-     * Unregisters for pointing info changed from satellite modem.
-     *
-     * @param h Handler to be removed from the registrant list.
-     */
-    default void unregisterForSatellitePointingInfoChanged(@NonNull Handler h) {}
-
-    /**
-     * Registers for mode changed from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    default void registerForSatelliteModeChanged(@NonNull Handler h,
-            int what, @Nullable Object obj) {}
-
-    /**
-     * Unregisters for mode changed from satellite modem.
-     *
-     * @param h Handler to be removed from the registrant list.
-     */
-    default void unregisterForSatelliteModeChanged(@NonNull Handler h) {}
-
-    /**
-     * Registers for radio technology changed from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    default void registerForSatelliteRadioTechnologyChanged(@NonNull Handler h,
-            int what, @Nullable Object obj) {}
-
-    /**
-     * Unregisters for radio technology changed from satellite modem.
-     *
-     * @param h Handler to be removed from the registrant list.
-     */
-    default void unregisterForSatelliteRadioTechnologyChanged(@NonNull Handler h) {}
-
-    /**
-     * Registers for provision state changed from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    default void registerForSatelliteProvisionStateChanged(@NonNull Handler h,
-            int what, @Nullable Object obj) {}
-
-    /**
-     * Unregisters for provision state changed from satellite modem.
-     *
-     * @param h Handler to be removed from the registrant list.
-     */
-    default void unregisterForSatelliteProvisionStateChanged(@NonNull Handler h) {}
+    default void unregisterForSecurityAlgorithmUpdates(Handler h) {}
 }
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index e5a5c8f..e81d0f1 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -41,9 +41,11 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
 
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * broadcast intents
@@ -55,10 +57,15 @@
 
     private TelephonyRegistryManager mTelephonyRegistryMgr;
 
+    /** Feature flags */
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
 
-    public DefaultPhoneNotifier(Context context) {
+
+    public DefaultPhoneNotifier(Context context, @NonNull FeatureFlags featureFlags) {
         mTelephonyRegistryMgr = (TelephonyRegistryManager) context.getSystemService(
             Context.TELEPHONY_REGISTRY_SERVICE);
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -125,8 +132,16 @@
 
     @Override
     public void notifyDataActivity(Phone sender) {
+
         int subId = sender.getSubId();
-        mTelephonyRegistryMgr.notifyDataActivityChanged(subId, sender.getDataActivityState());
+
+        if (mFeatureFlags.notifyDataActivityChangedWithSlot()) {
+            int phoneId = sender.getPhoneId();
+            mTelephonyRegistryMgr.notifyDataActivityChanged(phoneId, subId,
+                    sender.getDataActivityState());
+        } else {
+            mTelephonyRegistryMgr.notifyDataActivityChanged(subId, sender.getDataActivityState());
+        }
     }
 
     @Override
@@ -287,6 +302,11 @@
     }
 
     @Override
+    public void notifySimultaneousCellularCallingSubscriptionsChanged(Set<Integer> subIds) {
+        mTelephonyRegistryMgr.notifySimultaneousCellularCallingSubscriptionsChanged(subIds);
+    }
+
+    @Override
     public void notifyCallbackModeStarted(Phone sender, @EmergencyCallbackModeType int type) {
         mTelephonyRegistryMgr.notifyCallBackModeStarted(sender.getPhoneId(),
                 sender.getSubId(), type);
diff --git a/src/java/com/android/internal/telephony/DeviceStateMonitor.java b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
index ecc6208..7bdf2ff 100644
--- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java
+++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
@@ -22,6 +22,7 @@
 import static android.hardware.radio.V1_0.DeviceStateType.POWER_SAVE_MODE;
 import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
 
+import android.annotation.NonNull;
 import android.app.UiModeManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -47,6 +48,7 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
 
@@ -92,10 +94,14 @@
     private static final int NR_NSA_TRACKING_INDICATIONS_ALWAYS_ON = 2;
 
     private final Phone mPhone;
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
 
     private final LocalLog mLocalLog = new LocalLog(64);
 
     private final RegistrantList mPhysicalChannelConfigRegistrants = new RegistrantList();
+    private final RegistrantList mSignalStrengthReportDecisionCallbackRegistrants =
+            new RegistrantList();
 
     private final NetworkRequest mWifiNetworkRequest =
             new NetworkRequest.Builder()
@@ -269,8 +275,9 @@
      *
      * @param phone Phone object
      */
-    public DeviceStateMonitor(Phone phone) {
+    public DeviceStateMonitor(Phone phone, @NonNull FeatureFlags featureFlags) {
         mPhone = phone;
+        mFeatureFlags = featureFlags;
         DisplayManager dm = (DisplayManager) phone.getContext().getSystemService(
                 Context.DISPLAY_SERVICE);
         dm.registerDisplayListener(mDisplayListener, null);
@@ -602,6 +609,15 @@
             // use a null message since we don't care of receiving response
             mPhone.mCi.getBarringInfo(null);
         }
+
+        // Determine whether to notify registrants about the non-terrestrial signal strength change.
+        if (mFeatureFlags.oemEnabledSatelliteFlag()) {
+            if (shouldEnableSignalStrengthReports()) {
+                mSignalStrengthReportDecisionCallbackRegistrants.notifyResult(true);
+            } else {
+                mSignalStrengthReportDecisionCallbackRegistrants.notifyResult(false);
+            }
+        }
     }
 
     /**
@@ -778,6 +794,33 @@
     }
 
     /**
+     * Register a callback to decide whether signal strength should be notified or not.
+     * @param h Handler to notify
+     * @param what msg.what when the message is delivered
+     * @param obj AsyncResult.userObj when the message is delivered
+     */
+    public void registerForSignalStrengthReportDecision(Handler h, int what, Object obj) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            Rlog.d(TAG, "oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+        Registrant r = new Registrant(h, what, obj);
+        mSignalStrengthReportDecisionCallbackRegistrants.add(r);
+    }
+
+    /**
+     * Register a callback to decide whether signal strength should be notified or not.
+     * @param h Handler to notify
+     */
+    public void unregisterForSignalStrengthReportDecision(Handler h) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            Rlog.d(TAG, "oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+        mSignalStrengthReportDecisionCallbackRegistrants.remove(h);
+    }
+
+    /**
      * @param msg Debug message
      * @param logIntoLocalLog True if log into the local log
      */
diff --git a/src/java/com/android/internal/telephony/DisplayInfoController.java b/src/java/com/android/internal/telephony/DisplayInfoController.java
index c8f2248..e8a0566 100644
--- a/src/java/com/android/internal/telephony/DisplayInfoController.java
+++ b/src/java/com/android/internal/telephony/DisplayInfoController.java
@@ -31,6 +31,7 @@
 import android.util.LocalLog;
 import android.util.Pair;
 
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -75,12 +76,14 @@
     @NonNull private final NetworkTypeController mNetworkTypeController;
     @NonNull private final RegistrantList mTelephonyDisplayInfoChangedRegistrants =
             new RegistrantList();
+    @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private TelephonyDisplayInfo mTelephonyDisplayInfo;
     @NonNull private ServiceState mServiceState;
     @NonNull private PersistableBundle mConfigs;
 
-    public DisplayInfoController(@NonNull Phone phone) {
+    public DisplayInfoController(@NonNull Phone phone, @NonNull FeatureFlags featureFlags) {
         mPhone = phone;
+        mFeatureFlags = featureFlags;
         mLogTag = "DIC-" + mPhone.getPhoneId();
         mServiceState = mPhone.getServiceStateTracker().getServiceState();
         mConfigs = new PersistableBundle();
@@ -105,7 +108,7 @@
                 TelephonyManager.NETWORK_TYPE_UNKNOWN,
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 false);
-        mNetworkTypeController = new NetworkTypeController(phone, this);
+        mNetworkTypeController = new NetworkTypeController(phone, this, featureFlags);
         // EVENT_UPDATE will transition from DefaultState to the current state
         // and update the TelephonyDisplayInfo based on the current state.
         mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
@@ -147,7 +150,8 @@
      */
     private boolean isRoaming() {
         boolean roaming = mServiceState.getRoaming();
-        if (roaming && !mConfigs.getBoolean(CarrierConfigManager.KEY_SHOW_ROAMING_INDICATOR_BOOL)) {
+        if (roaming && mFeatureFlags.hideRoamingIcon()
+                && !mConfigs.getBoolean(CarrierConfigManager.KEY_SHOW_ROAMING_INDICATOR_BOOL)) {
             logl("Override roaming for display due to carrier configs.");
             roaming = false;
         }
diff --git a/src/java/com/android/internal/telephony/FdnUtils.java b/src/java/com/android/internal/telephony/FdnUtils.java
index aa2bcfd..23cab44 100644
--- a/src/java/com/android/internal/telephony/FdnUtils.java
+++ b/src/java/com/android/internal/telephony/FdnUtils.java
@@ -33,6 +33,7 @@
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
+import java.util.regex.PatternSyntaxException;
 
 /**
  * This is a basic utility class for common functions related to Fixed Dialing Numbers
@@ -123,6 +124,7 @@
             dialStrNational = String.valueOf(phoneNumber.getNationalNumber());
         } catch (NumberParseException ignored) {
             Rlog.w(LOG_TAG, "isFDN: could not parse dialStr");
+            dialStr = extractSMSC(dialStr);
         }
 
         /**
@@ -187,4 +189,37 @@
 
         return uiccProfile.getApplication(UiccController.APP_FAM_3GPP);
     }
+
+    private static String extractSMSC(String dialStr) {
+        try {
+            String[] dialStrParts = null;
+            if (dialStr.contains(",")) {
+                // SMSC can be in the format of ""+123456789123",123"
+                // Split into two parts using comma as delimiter
+                // and first part of the string is used as smsc address
+                dialStrParts = dialStr.split(",");
+            } else if (dialStr.contains("@")) {
+                // SMSC can be in the format of "+123456789123@ims.mnc.org"
+                // Split into two parts using @ as delimiter
+                // and first part of the string is used as smsc address
+                dialStrParts = dialStr.split("@");
+            }
+
+            if (dialStrParts != null && dialStrParts.length >= 1) {
+                if (dialStrParts[0].contains("\"")) {
+                    // If SMSC is in this format: ""+123456789123",123", after performing above
+                    // split we get string with double-quotation marks in it
+                    // dialStrParts[0] = ""+123456789123"".
+                    // Here, we remove double-quotation marks from the string.
+                    dialStrParts[0] = dialStrParts[0].replaceAll("\"", "");
+                }
+                return dialStrParts[0];
+            }
+        } catch (PatternSyntaxException ex) {
+            Rlog.w(LOG_TAG, "extractSMSC: Could not extract number from dialStr " + ex);
+        }
+
+        // Return original dialStr if it is not in any of the formats mentions above.
+        return dialStr;
+    }
 }
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index d76ee19..5517bc6 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -47,6 +47,7 @@
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.telephony.Rlog;
 
@@ -155,7 +156,9 @@
 
     //***** Constructors
 
-    public GsmCdmaCallTracker (GsmCdmaPhone phone) {
+    public GsmCdmaCallTracker(GsmCdmaPhone phone, FeatureFlags featureFlags) {
+        super(featureFlags);
+
         this.mPhone = phone;
         mCi = phone.mCi;
         mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index c8d5015..aca759b 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -16,10 +16,7 @@
 
 package com.android.internal.telephony;
 
-import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
-import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS_PS;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
-import static android.telephony.NetworkRegistrationInfo.DOMAIN_UNKNOWN;
 
 import static com.android.internal.telephony.CommandException.Error.GENERIC_FAILURE;
 import static com.android.internal.telephony.CommandException.Error.SIM_BUSY;
@@ -75,16 +72,17 @@
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.RadioPowerState;
-import android.telephony.AnomalyReporter;
 import android.telephony.BarringInfo;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellBroadcastIdRange;
 import android.telephony.CellIdentity;
+import android.telephony.CellularIdentifierDisclosure;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.NetworkScanRequest;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.RadioAccessFamily;
+import android.telephony.SecurityAlgorithmUpdate;
 import android.telephony.ServiceState;
 import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.SubscriptionInfo;
@@ -94,6 +92,7 @@
 import android.telephony.UssdResponse;
 import android.telephony.ims.ImsCallProfile;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 
@@ -107,6 +106,7 @@
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.GsmMmiCode;
 import com.android.internal.telephony.gsm.SsData;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
@@ -115,6 +115,9 @@
 import com.android.internal.telephony.imsphone.ImsPhoneMmiCode;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.metrics.VoiceCallSessionStats;
+import com.android.internal.telephony.security.CellularIdentifierDisclosureNotifier;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource;
+import com.android.internal.telephony.security.NullCipherNotifier;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService.SubscriptionManagerServiceCallback;
 import com.android.internal.telephony.test.SimulatedRadioControl;
@@ -145,7 +148,6 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
-import java.util.UUID;
 import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -182,11 +184,6 @@
     private static final String VM_NUMBER_CDMA = "vm_number_key_cdma";
     public static final int RESTART_ECM_TIMER = 0; // restart Ecm timer
     public static final int CANCEL_ECM_TIMER = 1; // cancel Ecm timer
-    private static final String PREFIX_WPS = "*272";
-    // WPS prefix when CLIR is being deactivated for the call.
-    private static final String PREFIX_WPS_CLIR_DEACTIVATE = "#31#*272";
-    // WPS prefix when CLIS is being activated for the call.
-    private static final String PREFIX_WPS_CLIR_ACTIVATE = "*31#*272";
     private CdmaSubscriptionSourceManager mCdmaSSM;
     public int mCdmaSubscriptionSource = CdmaSubscriptionSourceManager.SUBSCRIPTION_SOURCE_UNKNOWN;
     private PowerManager.WakeLock mWakeLock;
@@ -256,6 +253,8 @@
             CellBroadcastConfigTracker.make(this, null, true);
 
     private boolean mIsNullCipherAndIntegritySupported = false;
+    private boolean mIsIdentifierDisclosureTransparencySupported = false;
+    private boolean mIsNullCipherNotificationSupported = false;
 
     // Create Cfu (Call forward unconditional) so that dialing number &
     // mOnComplete (Message object passed by client) can be packed &
@@ -302,32 +301,58 @@
     private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener;
     private final CallWaitingController mCallWaitingController;
 
+    private CellularNetworkSecuritySafetySource mSafetySource;
+    private CellularIdentifierDisclosureNotifier mIdentifierDisclosureNotifier;
+    private NullCipherNotifier mNullCipherNotifier;
+
+    /**
+     * Temporary placeholder variables until b/312788638 is resolved, whereupon these should be
+     * ported to TelephonyManager.
+     */
+    // Set via Carrier Config
+    private static final Integer N1_MODE_DISALLOWED_REASON_CARRIER = 1;
+    // Set via a call to the method on Phone; the only caller is IMS, and all of this code will
+    // need to be updated to a voting mechanism (...enabled for reason...) if additional callers
+    // are desired.
+    private static final Integer N1_MODE_DISALLOWED_REASON_IMS = 2;
+
+    // Set of use callers/reasons why N1 Mode is disallowed. If the set is empty, it's allowed.
+    private final Set<Integer> mN1ModeDisallowedReasons = new ArraySet<>();
+
+    // If this value is null, then the modem value is unknown. If a caller explicitly sets the
+    // N1 mode, this value will be initialized before any attempt to set the value in the modem.
+    private Boolean mModemN1Mode = null;
+
     // Constructors
 
     public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier, int phoneId,
-                        int precisePhoneType, TelephonyComponentFactory telephonyComponentFactory) {
-        this(context, ci, notifier, false, phoneId, precisePhoneType, telephonyComponentFactory);
+                        int precisePhoneType, TelephonyComponentFactory telephonyComponentFactory,
+            @NonNull FeatureFlags featureFlags) {
+        this(context, ci, notifier, false, phoneId, precisePhoneType, telephonyComponentFactory,
+                featureFlags);
     }
 
     public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
                         boolean unitTestMode, int phoneId, int precisePhoneType,
-                        TelephonyComponentFactory telephonyComponentFactory) {
+                        TelephonyComponentFactory telephonyComponentFactory,
+            @NonNull FeatureFlags featureFlags) {
         this(context, ci, notifier,
                 unitTestMode, phoneId, precisePhoneType,
                 telephonyComponentFactory,
-                ImsManager::getInstance);
+                ImsManager::getInstance, featureFlags);
     }
 
     public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
             boolean unitTestMode, int phoneId, int precisePhoneType,
             TelephonyComponentFactory telephonyComponentFactory,
-            ImsManagerFactory imsManagerFactory) {
+            ImsManagerFactory imsManagerFactory, @NonNull FeatureFlags featureFlags) {
         super(precisePhoneType == PhoneConstants.PHONE_TYPE_GSM ? "GSM" : "CDMA",
-                notifier, context, ci, unitTestMode, phoneId, telephonyComponentFactory);
+                notifier, context, ci, unitTestMode, phoneId, telephonyComponentFactory,
+                featureFlags);
 
         // phone type needs to be set before other initialization as other objects rely on it
         mPrecisePhoneType = precisePhoneType;
-        mVoiceCallSessionStats = new VoiceCallSessionStats(mPhoneId, this);
+        mVoiceCallSessionStats = new VoiceCallSessionStats(mPhoneId, this, featureFlags);
         mImsManagerFactory = imsManagerFactory;
         initOnce(ci);
         initRatSpecific(precisePhoneType);
@@ -344,21 +369,22 @@
         mSignalStrengthController = mTelephonyComponentFactory.inject(
                 SignalStrengthController.class.getName()).makeSignalStrengthController(this);
         mSST = mTelephonyComponentFactory.inject(ServiceStateTracker.class.getName())
-                .makeServiceStateTracker(this, this.mCi);
+                .makeServiceStateTracker(this, this.mCi, featureFlags);
         mEmergencyNumberTracker = mTelephonyComponentFactory
                 .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker(
                         this, this.mCi);
         mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
-                .makeDeviceStateMonitor(this);
+                .makeDeviceStateMonitor(this, mFeatureFlags);
 
         // DisplayInfoController creates an OverrideNetworkTypeController, which uses
         // DeviceStateMonitor so needs to be crated after it is instantiated.
         mDisplayInfoController = mTelephonyComponentFactory.inject(
-                DisplayInfoController.class.getName()).makeDisplayInfoController(this);
+                DisplayInfoController.class.getName())
+                .makeDisplayInfoController(this, featureFlags);
 
         mDataNetworkController = mTelephonyComponentFactory.inject(
                 DataNetworkController.class.getName())
-                .makeDataNetworkController(this, getLooper());
+                .makeDataNetworkController(this, getLooper(), featureFlags);
 
         mCarrierResolver = mTelephonyComponentFactory.inject(CarrierResolver.class.getName())
                 .makeCarrierResolver(this);
@@ -444,7 +470,7 @@
         }
 
         mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
-                .makeGsmCdmaCallTracker(this);
+                .makeGsmCdmaCallTracker(this, mFeatureFlags);
         mIccPhoneBookIntManager = mTelephonyComponentFactory
                 .inject(IccPhoneBookInterfaceManager.class.getName())
                 .makeIccPhoneBookInterfaceManager(this);
@@ -453,7 +479,7 @@
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
         mIccSmsInterfaceManager = mTelephonyComponentFactory
                 .inject(IccSmsInterfaceManager.class.getName())
-                .makeIccSmsInterfaceManager(this);
+                .makeIccSmsInterfaceManager(this, mFeatureFlags);
 
         mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
         mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
@@ -500,9 +526,43 @@
         mContext.registerReceiver(mBroadcastReceiver, filter,
                 android.Manifest.permission.MODIFY_PHONE_STATE, null, Context.RECEIVER_EXPORTED);
 
-        mCDM = new CarrierKeyDownloadManager(this);
+        mCDM = new CarrierKeyDownloadManager(this, mFeatureFlags);
         mCIM = new CarrierInfoManager();
 
+        mCi.registerForImeiMappingChanged(this, EVENT_IMEI_MAPPING_CHANGED, null);
+
+        if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
+                || mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+            mSafetySource =
+                    mTelephonyComponentFactory.makeCellularNetworkSecuritySafetySource(mContext);
+        }
+
+        if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()) {
+            logi(
+                    "enable_identifier_disclosure_transparency_unsol_events is on. Registering for "
+                            + "cellular identifier disclosures from phone "
+                            + getPhoneId());
+            mIdentifierDisclosureNotifier =
+                    mTelephonyComponentFactory
+                            .inject(CellularIdentifierDisclosureNotifier.class.getName())
+                            .makeIdentifierDisclosureNotifier(mSafetySource);
+            mCi.registerForCellularIdentifierDisclosures(
+                    this, EVENT_CELL_IDENTIFIER_DISCLOSURE, null);
+        }
+
+        if (mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+            logi(
+                    "enable_modem_cipher_transparency_unsol_events is on. Registering for security "
+                            + "algorithm updates from phone "
+                            + getPhoneId());
+            mNullCipherNotifier =
+                    mTelephonyComponentFactory
+                            .inject(NullCipherNotifier.class.getName())
+                            .makeNullCipherNotifier();
+            mCi.registerForSecurityAlgorithmUpdates(
+                    this, EVENT_SECURITY_ALGORITHM_UPDATE, null);
+        }
+
         initializeCarrierApps();
     }
 
@@ -1415,9 +1475,7 @@
         }
 
         /** Check if the call is Wireless Priority Service call */
-        boolean isWpsCall = dialString != null ? (dialString.startsWith(PREFIX_WPS)
-                || dialString.startsWith(PREFIX_WPS_CLIR_ACTIVATE)
-                || dialString.startsWith(PREFIX_WPS_CLIR_DEACTIVATE)) : false;
+        boolean isWpsCall = PhoneNumberUtils.isWpsCallNumber(dialString);
 
         ImsPhone.ImsDialArgs.Builder imsDialArgsBuilder;
         imsDialArgsBuilder = ImsPhone.ImsDialArgs.Builder.from(dialArgs)
@@ -1440,24 +1498,6 @@
                 && (isWpsCall ? allowWpsOverIms : true);
 
         Bundle extras = dialArgs.intentExtras;
-        if (extras != null && extras.containsKey(PhoneConstants.EXTRA_COMPARE_DOMAIN)) {
-            int domain = extras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN);
-            if (!isEmergency && (!isMmiCode || isPotentialUssdCode)) {
-                if ((domain == DOMAIN_PS && !useImsForCall)
-                        || (domain == DOMAIN_CS && useImsForCall)
-                        || domain == DOMAIN_UNKNOWN || domain == DOMAIN_CS_PS) {
-                    loge("[Anomaly] legacy-useImsForCall:" + useImsForCall
-                            + ", NCDS-domain:" + domain);
-
-                    AnomalyReporter.reportAnomaly(
-                            UUID.fromString("bfae6c2e-ca2f-4121-b167-9cad26a3b353"),
-                            "Domain selection results don't match. useImsForCall:"
-                                    + useImsForCall + ", NCDS-domain:" + domain);
-                }
-            }
-            extras.remove(PhoneConstants.EXTRA_COMPARE_DOMAIN);
-        }
-
         // Only when the domain selection service is supported, EXTRA_DIAL_DOMAIN extra shall exist.
         if (extras != null && extras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN)) {
             int domain = extras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN);
@@ -1796,8 +1836,11 @@
             boolean check = true;
             for (int itr = 0;itr < dtmfString.length(); itr++) {
                 if (!PhoneNumberUtils.is12Key(dtmfString.charAt(itr))) {
-                    Rlog.e(LOG_TAG,
-                            "sendDtmf called with invalid character '" + dtmfString.charAt(itr)+ "'");
+                    Rlog.e(
+                            LOG_TAG,
+                            "sendDtmf called with invalid character '"
+                                    + dtmfString.charAt(itr)
+                                    + "'");
                     check = false;
                     break;
                 }
@@ -1864,15 +1907,15 @@
                 String spName = isPhoneTypeGsm() ? VM_NUMBER : VM_NUMBER_CDMA;
                 number = sp.getString(spName + getPhoneId(), null);
                 logd("getVoiceMailNumber: from " + spName + " number="
-                        + Rlog.pii(LOG_TAG, number));
+                        + Rlog.piiHandle(number));
             } else {
-                logd("getVoiceMailNumber: from IccRecords number=" + Rlog.pii(LOG_TAG, number));
+                logd("getVoiceMailNumber: from IccRecords number=" + Rlog.piiHandle(number));
             }
         }
         if (!isPhoneTypeGsm() && TextUtils.isEmpty(number)) {
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
             number = sp.getString(VM_NUMBER_CDMA + getPhoneId(), null);
-            logd("getVoiceMailNumber: from VM_NUMBER_CDMA number=" + number);
+            logd("getVoiceMailNumber: from VM_NUMBER_CDMA number=" + Rlog.piiHandle(number));
         }
 
         if (TextUtils.isEmpty(number)) {
@@ -1894,9 +1937,13 @@
                             && !mSST.isImsRegistered()) {
                         // roaming and IMS unregistered case if CC configured
                         number = defaultVmNumberRoamingAndImsUnregistered;
+                        logd("getVoiceMailNumber: from defaultVmNumberRoamingAndImsUnregistered "
+                                + "number=" + Rlog.piiHandle(number));
                     } else if (!TextUtils.isEmpty(defaultVmNumberRoaming)) {
                         // roaming default case if CC configured
                         number = defaultVmNumberRoaming;
+                        logd("getVoiceMailNumber: from defaultVmNumberRoaming number=" +
+                                Rlog.piiHandle(number));
                     }
                 }
             }
@@ -1910,12 +1957,13 @@
             if (b != null && b.getBoolean(
                     CarrierConfigManager.KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL)) {
                 number = getLine1Number();
+                logd("getVoiceMailNumber: from MSISDN number=" + Rlog.piiHandle(number));
             }
         }
-
         return number;
     }
 
+
     private String getVmSimImsi() {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         return sp.getString(VM_SIM_IMSI + getPhoneId(), null);
@@ -2349,11 +2397,89 @@
         return false;
     }
 
-    private void updateSsOverCdmaSupported(PersistableBundle b) {
-        if (b == null) return;
+    private void updateSsOverCdmaSupported(@NonNull PersistableBundle b) {
         mSsOverCdmaSupported = b.getBoolean(CarrierConfigManager.KEY_SUPPORT_SS_OVER_CDMA_BOOL);
     }
 
+    /**
+     * Enables or disables N1 mode (access to 5G core network) in accordance with
+     * 3GPP TS 24.501 4.9.
+     *
+     * <p> To prevent redundant calls down to the modem and to support a mechanism whereby
+     * N1 mode is only on if both IMS and carrier config believe that it should be on, this
+     * method will first sync the value from the modem prior to possibly setting it. In addition
+     * N1 mode will not be set to enabled unless both IMS and Carrier want it, since the use
+     * cases require all entities to agree lest it default to disabled.
+     *
+     * @param enable {@code true} to enable N1 mode, {@code false} to disable N1 mode.
+     * @param result Callback message to receive the result or null.
+     */
+    @Override
+    public void setN1ModeEnabled(boolean enable, @Nullable Message result) {
+        if (mFeatureFlags.enableCarrierConfigN1Control()) {
+            // This might be called by IMS on another thread, so to avoid the requirement to
+            // lock, post it through the handler.
+            post(() -> {
+                if (enable) {
+                    mN1ModeDisallowedReasons.remove(N1_MODE_DISALLOWED_REASON_IMS);
+                } else {
+                    mN1ModeDisallowedReasons.add(N1_MODE_DISALLOWED_REASON_IMS);
+                }
+                if (mModemN1Mode == null) {
+                    mCi.isN1ModeEnabled(obtainMessage(EVENT_GET_N1_MODE_ENABLED_DONE, result));
+                } else {
+                    maybeUpdateModemN1Mode(result);
+                }
+            });
+        } else {
+            super.setN1ModeEnabled(enable, result);
+        }
+    }
+
+    /** Only called on the handler thread. */
+    private void maybeUpdateModemN1Mode(@Nullable Message result) {
+        final boolean wantN1Enabled = mN1ModeDisallowedReasons.isEmpty();
+
+        logd("N1 Mode: isModemN1Enabled=" + mModemN1Mode + ", wantN1Enabled=" + wantN1Enabled);
+
+        // mModemN1Mode is never null here
+        if (mModemN1Mode != wantN1Enabled) {
+            // Assume success pending a response, which avoids multiple concurrent requests
+            // going down to the modem. If it fails, that is addressed in the response.
+            mModemN1Mode = wantN1Enabled;
+            super.setN1ModeEnabled(
+                    wantN1Enabled, obtainMessage(EVENT_SET_N1_MODE_ENABLED_DONE, result));
+        } else if (result != null) {
+            AsyncResult.forMessage(result);
+            result.sendToTarget();
+        }
+    }
+
+    /** Only called on the handler thread. */
+    private void updateCarrierN1ModeSupported(@NonNull PersistableBundle b) {
+        if (!mFeatureFlags.enableCarrierConfigN1Control()) return;
+
+        if (!CarrierConfigManager.isConfigForIdentifiedCarrier(b)) return;
+
+        final int[] supportedNrModes = b.getIntArray(
+                CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
+
+
+        if (ArrayUtils.contains(
+                supportedNrModes,
+                CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA)) {
+            mN1ModeDisallowedReasons.remove(N1_MODE_DISALLOWED_REASON_CARRIER);
+        } else {
+            mN1ModeDisallowedReasons.add(N1_MODE_DISALLOWED_REASON_CARRIER);
+        }
+
+        if (mModemN1Mode == null) {
+            mCi.isN1ModeEnabled(obtainMessage(EVENT_GET_N1_MODE_ENABLED_DONE));
+        } else {
+            maybeUpdateModemN1Mode(null);
+        }
+    }
+
     @Override
     public boolean useSsOverIms(Message onComplete) {
         boolean isUtEnabled = isUtEnabled();
@@ -2481,7 +2607,7 @@
             Bundle extras = new Bundle();
             extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
 
-            final TelecomManager telecomManager = TelecomManager.from(mContext);
+            final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
             telecomManager.placeCall(
                     Uri.fromParts(PhoneAccount.SCHEME_TEL, cfNumber, null), extras);
 
@@ -2734,13 +2860,15 @@
             mCi.setCallWaiting(enable, serviceClass, onComplete);
         } else if (mSsOverCdmaSupported) {
             String cwPrefix = CdmaMmiCode.getCallWaitingPrefix(enable);
-            Rlog.i(LOG_TAG, "setCallWaiting in CDMA : dial for set call waiting" + " prefix= " + cwPrefix);
+            Rlog.i(
+                    LOG_TAG,
+                    "setCallWaiting in CDMA : dial for set call waiting" + " prefix= " + cwPrefix);
 
             PhoneAccountHandle phoneAccountHandle = subscriptionIdToPhoneAccountHandle(getSubId());
             Bundle extras = new Bundle();
             extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
 
-            final TelecomManager telecomManager = TelecomManager.from(mContext);
+            final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
             telecomManager.placeCall(
                     Uri.fromParts(PhoneAccount.SCHEME_TEL, cwPrefix, null), extras);
 
@@ -3046,7 +3174,8 @@
         mCi.areUiccApplicationsEnabled(obtainMessage(EVENT_GET_UICC_APPS_ENABLEMENT_DONE));
 
         handleNullCipherEnabledChange();
-        startLceAfterRadioIsAvailable();
+        handleIdentifierDisclosureNotificationPreferenceChange();
+        handleNullCipherNotificationPreferenceChanged();
     }
 
     private void handleRadioOn() {
@@ -3092,19 +3221,7 @@
             }
             break;
             case EVENT_GET_DEVICE_IMEI_DONE :
-                ar = (AsyncResult)msg.obj;
-                if (ar.exception != null || ar.result == null) {
-                    loge("Exception received : " + ar.exception);
-                    break;
-                }
-                ImeiInfo imeiInfo = (ImeiInfo) ar.result;
-                if (!TextUtils.isEmpty(imeiInfo.imei)) {
-                    mImeiType = imeiInfo.type;
-                    mImei = imeiInfo.imei;
-                    mImeiSv = imeiInfo.svn;
-                } else {
-                    // TODO Report telephony anomaly
-                }
+                parseImeiInfo(msg);
                 break;
             case EVENT_GET_DEVICE_IDENTITY_DONE:{
                 ar = (AsyncResult)msg.obj;
@@ -3218,15 +3335,17 @@
 
                 CarrierConfigManager configMgr = (CarrierConfigManager)
                         getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-                PersistableBundle b = configMgr.getConfigForSubId(getSubId());
-
-                updateBroadcastEmergencyCallStateChangesAfterCarrierConfigChanged(b);
-
-                updateCdmaRoamingSettingsAfterCarrierConfigChanged(b);
-
-                updateNrSettingsAfterCarrierConfigChanged(b);
-                updateVoNrSettings(b);
-                updateSsOverCdmaSupported(b);
+                final PersistableBundle b = configMgr.getConfigForSubId(getSubId());
+                if (b != null) {
+                    updateBroadcastEmergencyCallStateChangesAfterCarrierConfigChanged(b);
+                    updateCdmaRoamingSettingsAfterCarrierConfigChanged(b);
+                    updateNrSettingsAfterCarrierConfigChanged(b);
+                    updateVoNrSettings(b);
+                    updateSsOverCdmaSupported(b);
+                    updateCarrierN1ModeSupported(b);
+                } else {
+                    loge("Failed to retrieve a carrier config bundle for subId=" + getSubId());
+                }
                 loadAllowedNetworksFromSubscriptionDatabase();
                 // Obtain new radio capabilities from the modem, since some are SIM-dependent
                 mCi.getRadioCapability(obtainMessage(EVENT_GET_RADIO_CAPABILITY));
@@ -3523,14 +3642,7 @@
             case EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE:
                 logd("EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE");
                 ar = (AsyncResult) msg.obj;
-                // Only test for a success here in order to flip the support flag.
-                // Testing for the negative case, e.g. REQUEST_NOT_SUPPORTED, is insufficient
-                // because the modem or the RIL could still return exceptions for temporary
-                // failures even when the feature is unsupported.
-                if (ar == null || ar.exception == null) {
-                    mIsNullCipherAndIntegritySupported = true;
-                    return;
-                }
+                mIsNullCipherAndIntegritySupported = doesResultIndicateModemSupport(ar);
                 break;
 
             case EVENT_IMS_DEREGISTRATION_TRIGGERED:
@@ -3553,11 +3665,118 @@
                     }
                 }
                 break;
+
+            case EVENT_GET_N1_MODE_ENABLED_DONE:
+                logd("EVENT_GET_N1_MODE_ENABLED_DONE");
+                ar = (AsyncResult) msg.obj;
+                if (ar == null || ar.exception != null
+                        || ar.result == null || !(ar.result instanceof Boolean)) {
+                    Rlog.e(LOG_TAG, "Failed to Retrieve N1 Mode", ar.exception);
+                    if (ar != null && ar.userObj instanceof Message) {
+                        // original requester's message is stashed in the userObj
+                        final Message rsp = (Message) ar.userObj;
+                        AsyncResult.forMessage(rsp, null, ar.exception);
+                        rsp.sendToTarget();
+                    }
+                    break;
+                }
+
+                mModemN1Mode = (Boolean) ar.result;
+                maybeUpdateModemN1Mode((Message) ar.userObj);
+                break;
+
+            case EVENT_SET_N1_MODE_ENABLED_DONE:
+                logd("EVENT_SET_N1_MODE_ENABLED_DONE");
+                ar = (AsyncResult) msg.obj;
+                if (ar == null || ar.exception != null) {
+                    Rlog.e(LOG_TAG, "Failed to Set N1 Mode", ar.exception);
+                    // Set failed, so we have no idea at this point.
+                    mModemN1Mode = null;
+                }
+                if (ar != null && ar.userObj instanceof Message) {
+                    // original requester's message is stashed in the userObj
+                    final Message rsp = (Message) ar.userObj;
+                    AsyncResult.forMessage(rsp, null, ar.exception);
+                    rsp.sendToTarget();
+                }
+                break;
+
+            case EVENT_IMEI_MAPPING_CHANGED:
+                logd("EVENT_GET_DEVICE_IMEI_CHANGE_DONE phoneId = " + getPhoneId());
+                parseImeiInfo(msg);
+                break;
+
+            case EVENT_CELL_IDENTIFIER_DISCLOSURE:
+                logd("EVENT_CELL_IDENTIFIER_DISCLOSURE phoneId = " + getPhoneId());
+
+                ar = (AsyncResult) msg.obj;
+                if (ar == null || ar.result == null || ar.exception != null) {
+                    Rlog.e(
+                            LOG_TAG,
+                            "Failed to process cellular identifier disclosure",
+                            ar.exception);
+                    break;
+                }
+
+                CellularIdentifierDisclosure disclosure = (CellularIdentifierDisclosure) ar.result;
+                if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
+                        && mIdentifierDisclosureNotifier != null
+                        && disclosure != null) {
+                    mIdentifierDisclosureNotifier.addDisclosure(mContext, getSubId(), disclosure);
+                }
+                break;
+
+            case EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE:
+                logd("EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE");
+                ar = (AsyncResult) msg.obj;
+                mIsIdentifierDisclosureTransparencySupported = doesResultIndicateModemSupport(ar);
+                break;
+
+            case EVENT_SECURITY_ALGORITHM_UPDATE:
+                logd("EVENT_SECURITY_ALGORITHM_UPDATE phoneId = " + getPhoneId());
+                if (mFeatureFlags.enableModemCipherTransparencyUnsolEvents()
+                        && mNullCipherNotifier != null) {
+                    ar = (AsyncResult) msg.obj;
+                    SecurityAlgorithmUpdate update = (SecurityAlgorithmUpdate) ar.result;
+                    mNullCipherNotifier.onSecurityAlgorithmUpdate(getPhoneId(), update);
+                }
+                break;
+
+            case EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE:
+                logd("EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE");
+                ar = (AsyncResult) msg.obj;
+                mIsNullCipherNotificationSupported = doesResultIndicateModemSupport(ar);
+                break;
+
             default:
                 super.handleMessage(msg);
         }
     }
 
+    private boolean doesResultIndicateModemSupport(AsyncResult ar) {
+        // We can only say that the modem supports a call without ambiguity if there
+        // is no exception set on the response.  Testing for REQUEST_NOT_SUPPORTED, is
+        // insufficient because the modem or the RIL could still return exceptions for temporary
+        // failures even when the feature is unsupported.
+        return (ar == null || ar.exception == null);
+    }
+
+    private void parseImeiInfo(Message msg) {
+        AsyncResult ar = (AsyncResult)msg.obj;
+        if (ar.exception != null || ar.result == null) {
+            loge("parseImeiInfo :: Exception received : " + ar.exception);
+            return;
+        }
+        ImeiInfo imeiInfo = (ImeiInfo) ar.result;
+        if (!TextUtils.isEmpty(imeiInfo.imei)) {
+            mImeiType = imeiInfo.type;
+            mImei = imeiInfo.imei;
+            mImeiSv = imeiInfo.svn;
+        } else {
+            loge("parseImeiInfo :: IMEI value is empty");
+        }
+    }
+
     /**
      * Check if a different SIM is inserted at this slot from the last time. Storing last subId
      * in SharedPreference for now to detect SIM change.
@@ -4408,7 +4627,6 @@
         } else {
             loge("deleteAndCreatePhone: newVoiceRadioTech=" + newVoiceRadioTech +
                     " is not CDMA or GSM (error) - aborting!");
-            return;
         }
     }
 
@@ -4789,7 +5007,7 @@
     }
 
     private PhoneAccountHandle subscriptionIdToPhoneAccountHandle(final int subId) {
-        final TelecomManager telecomManager = TelecomManager.from(mContext);
+        final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
         final TelephonyManager telephonyManager = TelephonyManager.from(mContext);
         final Iterator<PhoneAccountHandle> phoneAccounts =
             telecomManager.getCallCapablePhoneAccounts(true).listIterator();
@@ -4995,12 +5213,7 @@
     }
 
     private void updateBroadcastEmergencyCallStateChangesAfterCarrierConfigChanged(
-            PersistableBundle config) {
-        if (config == null) {
-            loge("didn't get broadcastEmergencyCallStateChanges from carrier config");
-            return;
-        }
-
+            @NonNull PersistableBundle config) {
         // get broadcastEmergencyCallStateChanges
         boolean broadcastEmergencyCallStateChanges = config.getBoolean(
                 CarrierConfigManager.KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL);
@@ -5008,11 +5221,7 @@
         setBroadcastEmergencyCallStateChanges(broadcastEmergencyCallStateChanges);
     }
 
-    private void updateNrSettingsAfterCarrierConfigChanged(PersistableBundle config) {
-        if (config == null) {
-            loge("didn't get the carrier_nr_availability_int from the carrier config.");
-            return;
-        }
+    private void updateNrSettingsAfterCarrierConfigChanged(@NonNull PersistableBundle config) {
         int[] nrAvailabilities = config.getIntArray(
                 CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
         mIsCarrierNrSupported = !ArrayUtils.isEmpty(nrAvailabilities);
@@ -5023,11 +5232,6 @@
             return;
         }
 
-        if (config == null) {
-            loge("didn't get the vonr_enabled_bool from the carrier config.");
-            return;
-        }
-
         boolean mIsVonrEnabledByCarrier =
                 config.getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL);
         boolean mDefaultVonr =
@@ -5052,12 +5256,8 @@
         mCi.setVoNrEnabled(enbleVonr, obtainMessage(EVENT_SET_VONR_ENABLED_DONE), null);
     }
 
-    private void updateCdmaRoamingSettingsAfterCarrierConfigChanged(PersistableBundle config) {
-        if (config == null) {
-            loge("didn't get the cdma_roaming_mode changes from the carrier config.");
-            return;
-        }
-
+    private void updateCdmaRoamingSettingsAfterCarrierConfigChanged(
+            @NonNull PersistableBundle config) {
         // Changing the cdma roaming settings based carrier config.
         int config_cdma_roaming_mode = config.getInt(
                 CarrierConfigManager.KEY_CDMA_ROAMING_MODE_INT);
@@ -5153,7 +5353,79 @@
     }
 
     @Override
+    public void handleIdentifierDisclosureNotificationPreferenceChange() {
+        if (!mFeatureFlags.enableIdentifierDisclosureTransparency()) {
+            logi("Not handling identifier disclosure preference change. Feature flag "
+                    + "enable_identifier_disclosure_transparency disabled");
+            return;
+        }
+        boolean prefEnabled = getIdentifierDisclosureNotificationsPreferenceEnabled();
+
+        // The notifier is tied to handling unsolicited updates from the modem, not the
+        // enable/disable API, so we only toggle the enable state if the unsol events feature
+        // flag is enabled.
+        if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()) {
+            if (prefEnabled) {
+                mIdentifierDisclosureNotifier.enable(mContext);
+            } else {
+                mIdentifierDisclosureNotifier.disable(mContext);
+            }
+        } else {
+            logi("Not toggling enable state for disclosure notifier. Feature flag "
+                    + "enable_identifier_disclosure_transparency_unsol_events is disabled");
+        }
+
+        mCi.setCellularIdentifierTransparencyEnabled(prefEnabled,
+                obtainMessage(EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE));
+    }
+
+    @Override
+    public void handleNullCipherNotificationPreferenceChanged() {
+        if (!mFeatureFlags.enableModemCipherTransparency()) {
+            logi("Not handling null cipher notification preference change. Feature flag "
+                    + "enable_modem_cipher_transparency disabled");
+            return;
+        }
+        boolean prefEnabled = getNullCipherNotificationsPreferenceEnabled();
+
+        // The notifier is tied to handling unsolicited updates from the modem, not the
+        // enable/disable API.
+        if (mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+            if (prefEnabled) {
+                mNullCipherNotifier.enable();
+            } else {
+                mNullCipherNotifier.disable();
+            }
+        } else {
+            logi(
+                    "Not toggling enable state for cipher notifier. Feature flag "
+                            + "enable_modem_cipher_transparency_unsol_events is disabled.");
+        }
+
+        mCi.setSecurityAlgorithmsUpdatedEnabled(prefEnabled,
+                obtainMessage(EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE));
+    }
+
+    @Override
     public boolean isNullCipherAndIntegritySupported() {
         return mIsNullCipherAndIntegritySupported;
     }
+
+    @Override
+    public boolean isIdentifierDisclosureTransparencySupported() {
+        return mIsIdentifierDisclosureTransparencySupported;
+    }
+
+    @Override
+    public boolean isNullCipherNotificationSupported() {
+        return mIsNullCipherNotificationSupported;
+    }
+
+    @Override
+    public void refreshSafetySources(String refreshBroadcastId) {
+        if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
+                || mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+            mSafetySource.refresh(mContext, refreshBroadcastId);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 2d77631..7c1670c 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -21,6 +21,7 @@
 import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -47,6 +48,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccConstants;
 import com.android.internal.telephony.uicc.IccFileHandler;
@@ -155,11 +157,11 @@
         }
     };
 
-    protected IccSmsInterfaceManager(Phone phone) {
+    protected IccSmsInterfaceManager(Phone phone, @NonNull FeatureFlags featureFlags) {
         this(phone, phone.getContext(),
                 (AppOpsManager) phone.getContext().getSystemService(Context.APP_OPS_SERVICE),
                 new SmsDispatchersController(
-                        phone, phone.mSmsStorageMonitor, phone.mSmsUsageMonitor),
+                        phone, phone.mSmsStorageMonitor, phone.mSmsUsageMonitor, featureFlags),
                 new SmsPermissions(phone, phone.getContext(),
                         (AppOpsManager) phone.getContext().getSystemService(
                                 Context.APP_OPS_SERVICE)));
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index 9b116b4..4e4d55d 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -39,6 +39,8 @@
 import com.android.ims.ImsManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.analytics.TelephonyAnalytics;
+import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.util.SMSDispatcherUtil;
@@ -200,10 +202,13 @@
                         tracker.onSent(mContext);
                         mTrackers.remove(token);
                         mPhone.notifySmsSent(tracker.mDestAddress);
+                        mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
+                                tracker.mDestAddress, tracker.mMessageId);
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR:
                         tracker.onFailed(mContext, reason, networkReasonCode);
                         mTrackers.remove(token);
+                        notifySmsSentFailedToEmergencyStateTracker(tracker);
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY:
                         int maxRetryCountOverIms = getMaxRetryCountOverIms();
@@ -222,6 +227,7 @@
                         } else {
                             tracker.onFailed(mContext, reason, networkReasonCode);
                             mTrackers.remove(token);
+                            notifySmsSentFailedToEmergencyStateTracker(tracker);
                         }
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK:
@@ -242,6 +248,18 @@
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
                         tracker.getInterval());
+                if (mPhone != null) {
+                    TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                    if (telephonyAnalytics != null) {
+                        SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                        if (smsMmsAnalytics != null) {
+                            smsMmsAnalytics.onOutgoingSms(
+                                    true /* isOverIms */,
+                                    reason);
+                        }
+                    }
+                }
+
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -642,6 +660,18 @@
                     tracker.mMessageId,
                     tracker.isFromDefaultSmsApplication(mContext),
                     tracker.getInterval());
+            if (mPhone != null) {
+                TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                if (telephonyAnalytics != null) {
+                    SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                    if (smsMmsAnalytics != null) {
+                        smsMmsAnalytics.onOutgoingSms(
+                                true /* isOverIms */,
+                                SmsManager.RESULT_SYSTEM_ERROR
+                        );
+                    }
+                }
+            }
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 9166719..eafb4ba 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -71,6 +71,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.SmsConstants.MessageClass;
+import com.android.internal.telephony.analytics.TelephonyAnalytics;
+import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.internal.telephony.util.TelephonyUtils;
@@ -745,6 +747,16 @@
         if (result != Intents.RESULT_SMS_HANDLED && result != Activity.RESULT_OK) {
             mMetrics.writeIncomingSmsError(mPhone.getPhoneId(), is3gpp2(), smsSource, result);
             mPhone.getSmsStats().onIncomingSmsError(is3gpp2(), smsSource, result);
+            if (mPhone != null) {
+                TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                if (telephonyAnalytics != null) {
+                    SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                    if (smsMmsAnalytics != null) {
+                        smsMmsAnalytics.onIncomingSmsError(smsSource, result);
+                    }
+                }
+            }
+
         }
         return result;
     }
@@ -1008,6 +1020,16 @@
             logeWithLocalLog(errorMsg, tracker.getMessageId());
             mPhone.getSmsStats().onIncomingSmsError(
                     is3gpp2(), tracker.getSource(), RESULT_SMS_NULL_PDU);
+            if (mPhone != null) {
+                TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                if (telephonyAnalytics != null) {
+                    SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                    if (smsMmsAnalytics != null) {
+                        smsMmsAnalytics.onIncomingSmsError(
+                                tracker.getSource(), RESULT_SMS_NULL_PDU);
+                    }
+                }
+            }
             return false;
         }
 
@@ -1082,7 +1104,16 @@
                 format, timestamps, block, tracker.getMessageId());
         mPhone.getSmsStats().onIncomingSmsSuccess(is3gpp2(), tracker.getSource(),
                 messageCount, block, tracker.getMessageId());
+        if (mPhone != null) {
+            TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+            if (telephonyAnalytics != null) {
+                SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                if (smsMmsAnalytics != null) {
+                    smsMmsAnalytics.onIncomingSmsSuccess(tracker.getSource());
+                }
+            }
 
+        }
         // Always invoke SMS filters, even if the number ends up being blocked, to prevent
         // surprising bugs due to blocking numbers that happen to be used for visual voicemail SMS
         // or other carrier system messages.
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
index 076b001..0afe119 100644
--- a/src/java/com/android/internal/telephony/LocaleTracker.java
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -41,6 +41,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.MccTable.MccMnc;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
@@ -154,6 +155,8 @@
     @Nullable
     private String mCurrentCountryIso;
 
+    @NonNull private final FeatureFlags mFeatureFlags;
+
     /** The country override for testing purposes */
     @Nullable
     private String mCountryOverride;
@@ -244,12 +247,14 @@
      * @param nitzStateMachine NITZ state machine
      * @param looper The looper message handler
      */
-    public LocaleTracker(Phone phone, NitzStateMachine nitzStateMachine, Looper looper)  {
+    public LocaleTracker(Phone phone, NitzStateMachine nitzStateMachine, Looper looper,
+            FeatureFlags featureFlags)  {
         super(looper);
         mPhone = phone;
         mNitzStateMachine = nitzStateMachine;
         mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
         mTag = LocaleTracker.class.getSimpleName() + "-" + mPhone.getPhoneId();
+        mFeatureFlags = featureFlags;
 
         final IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
@@ -560,6 +565,10 @@
                 TelephonyProperties.operator_iso_country(newProp);
             }
 
+            if (mFeatureFlags.oemEnabledSatelliteFlag()) {
+                TelephonyCountryDetector.getInstance(mPhone.getContext())
+                        .onNetworkCountryCodeChanged(mPhone, countryIso);
+            }
             Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
             intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryIso);
             intent.putExtra(TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY,
diff --git a/src/java/com/android/internal/telephony/ModemIndication.java b/src/java/com/android/internal/telephony/ModemIndication.java
index 0ee40bb..3893c6a 100644
--- a/src/java/com/android/internal/telephony/ModemIndication.java
+++ b/src/java/com/android/internal/telephony/ModemIndication.java
@@ -19,12 +19,14 @@
 import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM;
 
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_HARDWARE_CONFIG_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_IMEI_MAPPING_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_MODEM_RESTART;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RADIO_CAPABILITY;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RIL_CONNECTED;
 
 import android.hardware.radio.modem.IRadioModemIndication;
+import android.hardware.radio.modem.ImeiInfo;
 import android.os.AsyncResult;
 
 import java.util.ArrayList;
@@ -132,4 +134,18 @@
     public int getInterfaceVersion() {
         return IRadioModemIndication.VERSION;
     }
+
+    /**
+     * Indicates when there is a change in the IMEI with respect to the sim slot.
+     *
+     * @param imeiInfo IMEI information
+     */
+    public void onImeiMappingChanged(int indicationType, ImeiInfo imeiInfo) {
+        mRil.processIndication(HAL_SERVICE_MODEM, indicationType);
+
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogMore(RIL_UNSOL_IMEI_MAPPING_CHANGED, "ImeiMappingChanged");
+        }
+        mRil.notifyRegistrantsImeiMappingChanged(imeiInfo);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index d6b0930..8488ab0 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -420,22 +420,13 @@
             return;
         }
 
-        // b/153860050 Occasionally we receive carrier config change broadcast without subId
-        // being specified in it. So here we do additional check to make sur we don't miss the
-        // subId.
-        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            subId = SubscriptionManager.getSubscriptionId(phoneId);
-            if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                CarrierConfigManager cm = mContext.getSystemService(CarrierConfigManager.class);
-                if (cm != null && cm.getConfigForSubId(subId) != null) {
-                    loge("onCarrierConfigChanged with invalid subId while subId "
-                            + subId + " is active and its config is loaded");
-                }
+        CarrierConfigManager cm = mContext.getSystemService(CarrierConfigManager.class);
+        if (cm != null) {
+            if (CarrierConfigManager.isConfigForIdentifiedCarrier(cm.getConfigForSubId(subId))) {
+                mCarrierConfigLoadedSubIds[phoneId] = subId;
+                reEvaluateAll();
             }
         }
-
-        mCarrierConfigLoadedSubIds[phoneId] = subId;
-        reEvaluateAll();
     }
 
     /**
@@ -541,7 +532,7 @@
         boolean setDefaultData = true;
         List<SubscriptionInfo> activeSubList = mSubscriptionManagerService
                 .getActiveSubscriptionInfoList(mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
+                        mContext.getAttributionTag(), true/*isForAllProfile*/);
         for (SubscriptionInfo activeInfo : activeSubList) {
             if (!(groupUuid.equals(activeInfo.getGroupUuid()))) {
                 // Do not set refSubId as defaultDataSubId if there are other active
@@ -588,7 +579,7 @@
 
         List<SubscriptionInfo> activeSubInfos = mSubscriptionManagerService
                 .getActiveSubscriptionInfoList(mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
+                        mContext.getAttributionTag(), true/*isForAllProfile*/);
 
         if (ArrayUtils.isEmpty(activeSubInfos)) {
             mPrimarySubList.clear();
diff --git a/src/java/com/android/internal/telephony/NetworkIndication.java b/src/java/com/android/internal/telephony/NetworkIndication.java
index 7f9ff79..5c49234 100644
--- a/src/java/com/android/internal/telephony/NetworkIndication.java
+++ b/src/java/com/android/internal/telephony/NetworkIndication.java
@@ -30,9 +30,11 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESTRICTED_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SECURITY_ALGORITHMS_UPDATED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIGNAL_STRENGTH;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SUPP_SVC_NOTIFICATION;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_VOICE_RADIO_TECH_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CELLULAR_IDENTIFIER_DISCLOSED;
 
 import android.annotation.ElapsedRealtimeLong;
 import android.hardware.radio.network.IRadioNetworkIndication;
@@ -42,10 +44,12 @@
 import android.telephony.BarringInfo;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
-import android.telephony.EmergencyRegResult;
+import android.telephony.CellularIdentifierDisclosure;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhysicalChannelConfig;
+import android.telephony.SecurityAlgorithmUpdate;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.text.TextUtils;
@@ -128,7 +132,7 @@
             android.hardware.radio.network.LinkCapacityEstimate lce) {
         mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
-        List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
+        List<LinkCapacityEstimate> response = RILUtils.convertHalLinkCapacityEstimate(lce);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
@@ -168,7 +172,10 @@
                 }
                 if (band == PhysicalChannelConfig.BAND_UNKNOWN) {
                     mRil.riljLoge("Unsupported unknown band.");
-                    return;
+                    // TODO, b/288310456,
+                    //  If the band is unknown, PhysicalChannelConfig can be built without setBand.
+                    //  It should be enforced not to allow "unknown" bands in the near future.
+                    // return;
                 } else {
                     builder.setBand(band);
                 }
@@ -204,9 +211,8 @@
             android.hardware.radio.network.SignalStrength signalStrength) {
         mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
-        SignalStrength ssInitial = RILUtils.convertHalSignalStrength(signalStrength);
+        SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength);
 
-        SignalStrength ss = mRil.fixupSignalStrength10(ssInitial);
         // Note this is set to "verbose" because it happens frequently
         if (mRil.isLogvOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
 
@@ -409,7 +415,7 @@
             android.hardware.radio.network.EmergencyRegResult result) {
         mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
-        EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(result);
+        EmergencyRegistrationResult response = RILUtils.convertHalEmergencyRegResult(result);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT, response);
@@ -419,6 +425,46 @@
                 new AsyncResult(null, response, null));
     }
 
+    /**
+     * Cellular identifier disclosure events
+     * @param indicationType Type of radio indication
+     * @param identifierDisclsoure the result of the Emergency Network Scan
+     */
+    public void cellularIdentifierDisclosed(int indicationType,
+            android.hardware.radio.network.CellularIdentifierDisclosure identifierDisclsoure) {
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
+
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_CELLULAR_IDENTIFIER_DISCLOSED, identifierDisclsoure);
+        }
+
+        CellularIdentifierDisclosure disclosure =
+                RILUtils.convertCellularIdentifierDisclosure(identifierDisclsoure);
+
+        mRil.mCellularIdentifierDisclosedRegistrants.notifyRegistrants(
+                new AsyncResult(null, disclosure, null));
+    }
+
+    /**
+     * Security algorithm update events
+     * @param indicationType Type of radio indication
+     * @param securityAlgorithmUpdate details of what changed
+     */
+    public void securityAlgorithmsUpdated(int indicationType,
+            android.hardware.radio.network.SecurityAlgorithmUpdate securityAlgorithmUpdate) {
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
+
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_SECURITY_ALGORITHMS_UPDATED, securityAlgorithmUpdate);
+        }
+
+        SecurityAlgorithmUpdate update =
+                RILUtils.convertSecurityAlgorithmUpdate(securityAlgorithmUpdate);
+
+        mRil.mSecurityAlgorithmUpdatedRegistrants.notifyRegistrants(
+                new AsyncResult(null, update, null));
+    }
+
     @Override
     public String getInterfaceHash() {
         return IRadioNetworkIndication.HASH;
diff --git a/src/java/com/android/internal/telephony/NetworkResponse.java b/src/java/com/android/internal/telephony/NetworkResponse.java
index b1eb926..b4a37b3 100644
--- a/src/java/com/android/internal/telephony/NetworkResponse.java
+++ b/src/java/com/android/internal/telephony/NetworkResponse.java
@@ -24,13 +24,11 @@
 import android.os.AsyncResult;
 import android.telephony.BarringInfo;
 import android.telephony.CellInfo;
-import android.telephony.EmergencyRegResult;
-import android.telephony.LinkCapacityEstimate;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.RadioAccessSpecifier;
 import android.telephony.SignalStrength;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Interface declaring response functions to solicited radio requests for network APIs.
@@ -269,23 +267,6 @@
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
-     * @param lceInfo LceDataInfo indicating LCE data
-     */
-    public void pullLceDataResponse(RadioResponseInfo responseInfo,
-            android.hardware.radio.network.LceDataInfo lceInfo) {
-        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
-
-        if (rr != null) {
-            List<LinkCapacityEstimate> ret = RILUtils.convertHalLceData(lceInfo);
-            if (responseInfo.error == RadioError.NONE) {
-                RadioResponse.sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    /**
-     * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setAllowedNetworkTypesBitmapResponse(RadioResponseInfo responseInfo) {
         RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
@@ -448,7 +429,7 @@
         RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
-            EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(regState);
+            EmergencyRegistrationResult response = RILUtils.convertHalEmergencyRegResult(regState);
             if (responseInfo.error == RadioError.NONE) {
                 RadioResponse.sendMessageResponse(rr.mResult, response);
             }
@@ -523,6 +504,53 @@
         RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setCellularIdentifierTransparencyEnabledResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param isEnabled Indicates whether cellular identifier disclosure transparency from the modem
+     *                  is enabled.
+     */
+    public void isCellularIdentifierTransparencyEnabledResponse(RadioResponseInfo responseInfo,
+                                                        boolean isEnabled) {
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, isEnabled);
+            }
+            mRil.processResponseDone(rr, responseInfo, isEnabled);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setSecurityAlgorithmsUpdatedEnabledResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param isEnabled Indicates whether security algorithm updates from the modem are enabled.
+     */
+    public void isSecurityAlgorithmsUpdatedEnabledResponse(RadioResponseInfo responseInfo,
+                                                        boolean isEnabled) {
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, isEnabled);
+            }
+            mRil.processResponseDone(rr, responseInfo, isEnabled);
+        }
+    }
+
     @Override
     public String getInterfaceHash() {
         return IRadioNetworkResponse.HASH;
diff --git a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
index 7567566..c09f8fb 100644
--- a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
+++ b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
@@ -42,7 +42,9 @@
 import android.telephony.TelephonyScanManager;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.util.ArrayUtils;
 
 import java.util.Collection;
 import java.util.List;
@@ -131,7 +133,7 @@
     }
 
     private boolean isValidScan(NetworkScanRequestInfo nsri) {
-        if (nsri.mRequest == null || nsri.mRequest.getSpecifiers() == null) {
+        if (nsri.mRequest == null || ArrayUtils.isEmpty(nsri.mRequest.getSpecifiers())) {
             return false;
         }
         if (nsri.mRequest.getSpecifiers().length > NetworkScanRequest.MAX_RADIO_ACCESS_NETWORKS) {
@@ -251,9 +253,10 @@
     /**
     * Tracks info about the radio network scan.
      *
-    * Also used to notice when the calling process dies so we can self-expire.
+    * Also used to notice when the calling process dies, so we can self-expire.
     */
-    class NetworkScanRequestInfo implements IBinder.DeathRecipient {
+    @VisibleForTesting
+    public class NetworkScanRequestInfo implements IBinder.DeathRecipient {
         private final NetworkScanRequest mRequest;
         private final Messenger mMessenger;
         private final IBinder mBinder;
@@ -265,8 +268,9 @@
         private final String mCallingPackage;
         private boolean mIsBinderDead;
 
-        NetworkScanRequestInfo(NetworkScanRequest r, Messenger m, IBinder b, int id, Phone phone,
-                int callingUid, int callingPid, String callingPackage,
+        @VisibleForTesting
+        public NetworkScanRequestInfo(NetworkScanRequest r, Messenger m, IBinder b, int id,
+                Phone phone, int callingUid, int callingPid, String callingPackage,
                 boolean renounceFineLocationAccess) {
             super();
             mRequest = r;
@@ -445,6 +449,10 @@
                 Log.e(TAG, "EVENT_RECEIVE_NETWORK_SCAN_RESULT: nsri is null");
                 return;
             }
+            if (nsri != mLiveRequestInfo) {
+                Log.e(TAG, "EVENT_RECEIVE_NETWORK_SCAN_RESULT received for inactive scan");
+                return;
+            }
             LocationAccessPolicy.LocationPermissionQuery locationQuery =
                     new LocationAccessPolicy.LocationPermissionQuery.Builder()
                     .setCallingPackage(nsri.mCallingPackage)
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index e6fb84e..b9ad388 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -37,10 +37,14 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataCallResponse.LinkStatus;
+import android.telephony.data.EpsQos;
+import android.telephony.data.NrQos;
+import android.telephony.data.QosBearerSession;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataUtils;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.util.IState;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
@@ -74,6 +78,7 @@
     private static final String ICON_5G = "5g";
     private static final String ICON_5G_PLUS = "5g_plus";
     private static final String STATE_CONNECTED_NR_ADVANCED = "connected_mmwave";
+    private static final String STATE_CONNECTED_RRC_IDLE = "connected_rrc_idle";
     private static final String STATE_CONNECTED = "connected";
     private static final String STATE_NOT_RESTRICTED_RRC_IDLE = "not_restricted_rrc_idle";
     private static final String STATE_NOT_RESTRICTED_RRC_CON = "not_restricted_rrc_con";
@@ -81,8 +86,8 @@
     private static final String STATE_ANY = "any";
     private static final String STATE_LEGACY = "legacy";
     private static final String[] ALL_STATES = {STATE_CONNECTED_NR_ADVANCED, STATE_CONNECTED,
-            STATE_NOT_RESTRICTED_RRC_IDLE, STATE_NOT_RESTRICTED_RRC_CON, STATE_RESTRICTED,
-            STATE_LEGACY };
+            STATE_CONNECTED_RRC_IDLE, STATE_NOT_RESTRICTED_RRC_IDLE, STATE_NOT_RESTRICTED_RRC_CON,
+            STATE_RESTRICTED, STATE_LEGACY };
 
     /** Stop all timers and go to current state. */
     public static final int EVENT_UPDATE = 0;
@@ -110,8 +115,10 @@
     private static final int EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED = 11;
     /** Event for device idle mode changed, when device goes to deep sleep and pauses all timers. */
     private static final int EVENT_DEVICE_IDLE_MODE_CHANGED = 12;
+    /** Event for qos sessions changed. */
+    private static final int EVENT_QOS_SESSION_CHANGED = 13;
 
-    private static final String[] sEvents = new String[EVENT_DEVICE_IDLE_MODE_CHANGED + 1];
+    private static final String[] sEvents = new String[EVENT_QOS_SESSION_CHANGED + 1];
     static {
         sEvents[EVENT_UPDATE] = "EVENT_UPDATE";
         sEvents[EVENT_QUIT] = "EVENT_QUIT";
@@ -127,10 +134,12 @@
         sEvents[EVENT_INITIALIZE] = "EVENT_INITIALIZE";
         sEvents[EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED] = "EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED";
         sEvents[EVENT_DEVICE_IDLE_MODE_CHANGED] = "EVENT_DEVICE_IDLE_MODE_CHANGED";
+        sEvents[EVENT_QOS_SESSION_CHANGED] = "EVENT_QOS_SESSION_CHANGED";
     }
 
     @NonNull private final Phone mPhone;
     @NonNull private final DisplayInfoController mDisplayInfoController;
+    @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -155,6 +164,30 @@
                 }
             };
 
+    @NonNull private final DataNetworkControllerCallback mDataNetworkControllerCallback =
+            new DataNetworkControllerCallback(getHandler()::post) {
+                @Override
+                public void onQosSessionsChanged(
+                        @NonNull List<QosBearerSession> qosBearerSessions) {
+                    if (!mIsTimerResetEnabledOnVoiceQos) return;
+                    sendMessage(obtainMessage(EVENT_QOS_SESSION_CHANGED, qosBearerSessions));
+                }
+
+                @Override
+                public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) {
+                    if (mNrAdvancedCapablePcoId <= 0) return;
+                    log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable);
+                    mIsNrAdvancedAllowedByPco = nrAdvancedCapable;
+                    sendMessage(EVENT_UPDATE);
+                }
+
+                @Override
+                public void onPhysicalLinkStatusChanged(@LinkStatus int status) {
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) return;
+                    sendMessage(obtainMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED, status));
+                }
+            };
+
     @NonNull private Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
     @NonNull private String mLteEnhancedPattern = "";
     @Annotation.OverrideNetworkType private int mOverrideNetworkType;
@@ -162,6 +195,10 @@
     private boolean mIsPrimaryTimerActive;
     private boolean mIsSecondaryTimerActive;
     private boolean mIsTimerResetEnabledForLegacyStateRrcIdle;
+    /** Carrier config to reset timers when mccmnc changes */
+    private boolean mIsTimerResetEnabledOnPlmnChanges;
+    /** Carrier config to reset timers when QCI(LTE) or 5QI(NR) is 1(conversational voice) */
+    private boolean mIsTimerResetEnabledOnVoiceQos;
     private int mLtePlusThresholdBandwidth;
     private int mNrAdvancedThresholdBandwidth;
     private boolean mIncludeLteForNrAdvancedThresholdBandwidth;
@@ -169,6 +206,8 @@
     @NonNull private final Set<Integer> mAdditionalNrAdvancedBands = new HashSet<>();
     @NonNull private String mPrimaryTimerState;
     @NonNull private String mSecondaryTimerState;
+    // TODO(b/316425811 remove the workaround)
+    private int mNrAdvancedBandsSecondaryTimer;
     @NonNull private String mPreviousState;
     @LinkStatus private int mPhysicalLinkStatus;
     private boolean mIsPhysicalChannelConfig16Supported;
@@ -177,9 +216,7 @@
     private boolean mIsUsingUserDataForRrcDetection = false;
     private boolean mEnableNrAdvancedWhileRoaming = true;
     private boolean mIsDeviceIdleMode = false;
-
-    @Nullable private DataNetworkControllerCallback mNrAdvancedCapableByPcoChangedCallback = null;
-    @Nullable private DataNetworkControllerCallback mNrPhysicalLinkStatusChangedCallback = null;
+    private boolean mPrimaryCellChangedWhileIdle = false;
 
     // Cached copies below to prevent race conditions
     @NonNull private ServiceState mServiceState;
@@ -187,18 +224,24 @@
 
     // Ratchet physical channel config fields to prevent 5G/5G+ flickering
     @NonNull private Set<Integer> mRatchetedNrBands = new HashSet<>();
+    // TODO(b/316425811 remove the workaround)
+    private boolean mLastShownNrDueToAdvancedBand = false;
     private int mRatchetedNrBandwidths = 0;
     private int mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
+    private boolean mDoesPccListIndicateIdle = false;
 
     /**
      * NetworkTypeController constructor.
      *
      * @param phone Phone object.
      * @param displayInfoController DisplayInfoController to send override network types to.
+     * @param featureFlags FeatureFlags controlling what icon features are enabled.
      */
-    public NetworkTypeController(Phone phone, DisplayInfoController displayInfoController) {
+    public NetworkTypeController(Phone phone, DisplayInfoController displayInfoController,
+            FeatureFlags featureFlags) {
         super(TAG, displayInfoController);
         mPhone = phone;
+        mFeatureFlags = featureFlags;
         mDisplayInfoController = displayInfoController;
         mOverrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
         mIsPhysicalChannelConfigOn = true;
@@ -210,6 +253,7 @@
         addState(mLegacyState, defaultState);
         addState(mIdleState, defaultState);
         addState(mLteConnectedState, defaultState);
+        addState(mNrIdleState, defaultState);
         addState(mNrConnectedState, defaultState);
         addState(mNrConnectedAdvancedState, defaultState);
         setInitialState(defaultState);
@@ -262,6 +306,8 @@
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
         mPhone.getDeviceStateMonitor().registerForPhysicalChannelConfigNotifChanged(getHandler(),
                 EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, null);
+        mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
+                mDataNetworkControllerCallback);
         IntentFilter filter = new IntentFilter();
         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
         mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
@@ -274,6 +320,8 @@
         mPhone.unregisterForPreferredNetworkTypeChanged(getHandler());
         mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
         mPhone.getDeviceStateMonitor().unregisterForPhysicalChannelConfigNotifChanged(getHandler());
+        mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
+                mDataNetworkControllerCallback);
         mPhone.getContext().unregisterReceiver(mIntentReceiver);
         CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
         if (mCarrierConfigChangeListener != null) {
@@ -295,6 +343,10 @@
                 CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
         mIsTimerResetEnabledForLegacyStateRrcIdle = config.getBoolean(
                 CarrierConfigManager.KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL);
+        mIsTimerResetEnabledOnPlmnChanges = config.getBoolean(
+                CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL);
+        mIsTimerResetEnabledOnVoiceQos = config.getBoolean(
+                CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL);
         mLtePlusThresholdBandwidth = config.getInt(
                 CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT);
         mNrAdvancedThresholdBandwidth = config.getInt(
@@ -313,43 +365,10 @@
         }
         mNrAdvancedCapablePcoId = config.getInt(
                 CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT);
-        if (mNrAdvancedCapablePcoId > 0 && mNrAdvancedCapableByPcoChangedCallback == null) {
-            mNrAdvancedCapableByPcoChangedCallback =
-                    new DataNetworkControllerCallback(getHandler()::post) {
-                        @Override
-                        public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) {
-                            log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable);
-                            mIsNrAdvancedAllowedByPco = nrAdvancedCapable;
-                            sendMessage(EVENT_UPDATE);
-                        }
-                    };
-            mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
-                    mNrAdvancedCapableByPcoChangedCallback);
-        } else if (mNrAdvancedCapablePcoId == 0 && mNrAdvancedCapableByPcoChangedCallback != null) {
-            mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
-                    mNrAdvancedCapableByPcoChangedCallback);
-            mNrAdvancedCapableByPcoChangedCallback = null;
-        }
         mIsUsingUserDataForRrcDetection = config.getBoolean(
                 CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL);
-        if (!isUsingPhysicalChannelConfigForRrcDetection()) {
-            if (mNrPhysicalLinkStatusChangedCallback == null) {
-                mNrPhysicalLinkStatusChangedCallback =
-                        new DataNetworkControllerCallback(getHandler()::post) {
-                            @Override
-                            public void onPhysicalLinkStatusChanged(@LinkStatus int status) {
-                                sendMessage(obtainMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
-                                        new AsyncResult(null, status, null)));
-                            }
-                        };
-                mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
-                        mNrPhysicalLinkStatusChangedCallback);
-            }
-        } else if (mNrPhysicalLinkStatusChangedCallback != null) {
-            mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
-                    mNrPhysicalLinkStatusChangedCallback);
-            mNrPhysicalLinkStatusChangedCallback = null;
-        }
+        mNrAdvancedBandsSecondaryTimer = config.getInt(
+                CarrierConfigManager.KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT);
         String nrIconConfiguration = config.getString(
                 CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
         String overrideTimerRule = config.getString(
@@ -357,7 +376,8 @@
         String overrideSecondaryTimerRule = config.getString(
                 CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
         createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
-        updatePhysicalChannelConfigs();
+        updatePhysicalChannelConfigs(
+                mPhone.getServiceStateTracker().getPhysicalChannelConfigList());
     }
 
     private void createTimerRules(String icons, String timers, String secondaryTimers) {
@@ -370,6 +390,9 @@
                     if (DBG) loge("Invalid 5G icon configuration, config = " + pair);
                     continue;
                 }
+                if (!mFeatureFlags.supportNrSaRrcIdle() && kv[0].equals(STATE_CONNECTED_RRC_IDLE)) {
+                    continue;
+                }
                 int icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
                 if (kv[1].equals(ICON_5G)) {
                     icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
@@ -397,6 +420,9 @@
                     if (DBG) loge("Invalid 5G icon timer configuration, config = " + triple);
                     continue;
                 }
+                if (!mFeatureFlags.supportNrSaRrcIdle() && kv[0].equals(STATE_CONNECTED_RRC_IDLE)) {
+                    continue;
+                }
                 int duration;
                 try {
                     duration = Integer.parseInt(kv[2]);
@@ -405,6 +431,10 @@
                 }
                 if (kv[0].equals(STATE_ANY)) {
                     for (String state : ALL_STATES) {
+                        if (!mFeatureFlags.supportNrSaRrcIdle()
+                                && state.equals(STATE_CONNECTED_RRC_IDLE)) {
+                            continue;
+                        }
                         OverrideTimerRule node = tempRules.get(state);
                         node.addTimer(kv[1], duration);
                     }
@@ -425,6 +455,9 @@
                     }
                     continue;
                 }
+                if (kv[0].equals(STATE_CONNECTED_RRC_IDLE) && !mFeatureFlags.supportNrSaRrcIdle()) {
+                    continue;
+                }
                 int duration;
                 try {
                     duration = Integer.parseInt(kv[2]);
@@ -433,6 +466,10 @@
                 }
                 if (kv[0].equals(STATE_ANY)) {
                     for (String state : ALL_STATES) {
+                        if (state.equals(STATE_CONNECTED_RRC_IDLE)
+                                && !mFeatureFlags.supportNrSaRrcIdle()) {
+                            continue;
+                        }
                         OverrideTimerRule node = tempRules.get(state);
                         node.addSecondaryTimer(kv[1], duration);
                     }
@@ -443,6 +480,23 @@
             }
         }
 
+        // TODO: Remove this workaround to make STATE_CONNECTED_RRC_IDLE backwards compatible with
+        //  STATE_CONNECTED once carrier configs are updated.
+        if (mFeatureFlags.supportNrSaRrcIdle()) {
+            OverrideTimerRule nrRules = tempRules.get(STATE_CONNECTED);
+            if (!tempRules.get(STATE_CONNECTED_RRC_IDLE).isDefined() && nrRules.isDefined()) {
+                OverrideTimerRule nrIdleRules =
+                        new OverrideTimerRule(STATE_CONNECTED_RRC_IDLE, nrRules.mOverrideType);
+                for (Map.Entry<String, Integer> entry : nrIdleRules.mPrimaryTimers.entrySet()) {
+                    nrIdleRules.addTimer(entry.getKey(), entry.getValue());
+                }
+                for (Map.Entry<String, Integer> entry : nrIdleRules.mSecondaryTimers.entrySet()) {
+                    nrIdleRules.addSecondaryTimer(entry.getKey(), entry.getValue());
+                }
+                tempRules.put(STATE_CONNECTED_RRC_IDLE, nrIdleRules);
+            }
+        }
+
         mOverrideTimerRules = tempRules;
         if (DBG) log("mOverrideTimerRules: " + mOverrideTimerRules);
     }
@@ -555,6 +609,7 @@
     private final class DefaultState extends State {
         @Override
         public boolean processMessage(Message msg) {
+            AsyncResult ar;
             if (DBG) log("DefaultState: process " + getEventName(msg.what));
             switch (msg.what) {
                 case EVENT_UPDATE:
@@ -575,17 +630,15 @@
                     parseCarrierConfigs();
                     break;
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     transitionToCurrentState();
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED:
-                    AsyncResult result = (AsyncResult) msg.obj;
-                    mIsPhysicalChannelConfigOn = (boolean) result.result;
+                    ar = (AsyncResult) msg.obj;
+                    mIsPhysicalChannelConfigOn = (boolean) ar.result;
                     if (DBG) {
                         log("mIsPhysicalChannelConfigOn changed to: " + mIsPhysicalChannelConfigOn);
                     }
@@ -612,11 +665,17 @@
                     mIsSecondaryTimerActive = false;
                     mSecondaryTimerState = "";
                     updateTimers();
+                    mLastShownNrDueToAdvancedBand = false;
                     updateOverrideNetworkType();
                     break;
                 case EVENT_RADIO_OFF_OR_UNAVAILABLE:
                     if (DBG) log("Reset timers since radio is off or unavailable.");
                     resetAllTimers();
+                    mRatchetedNrBands.clear();
+                    mRatchetedNrBandwidths = 0;
+                    mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
+                    mDoesPccListIndicateIdle = false;
+                    mPhysicalChannelConfigs = null;
                     transitionTo(mLegacyState);
                     break;
                 case EVENT_PREFERRED_NETWORK_MODE_CHANGED:
@@ -625,7 +684,8 @@
                     transitionToCurrentState();
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
@@ -643,6 +703,24 @@
                     }
                     transitionToCurrentState();
                     break;
+                case EVENT_QOS_SESSION_CHANGED:
+                    List<QosBearerSession> qosBearerSessions = (List<QosBearerSession>) msg.obj;
+                    boolean inVoiceCall = false;
+                    for (QosBearerSession session : qosBearerSessions) {
+                        // TS 23.203 23.501 - 1 means conversational voice
+                        if (session.getQos() instanceof EpsQos qos) {
+                            inVoiceCall = qos.getQci() == 1;
+                        } else if (session.getQos() instanceof NrQos qos) {
+                            inVoiceCall = qos.get5Qi() == 1;
+                        }
+                        if (inVoiceCall) {
+                            if (DBG) log("Device in voice call, reset all timers");
+                            resetAllTimers();
+                            transitionToCurrentState();
+                            break;
+                        }
+                    }
+                    break;
                 default:
                     throw new RuntimeException("Received invalid event: " + msg.what);
             }
@@ -677,10 +755,10 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("LegacyState: process " + getEventName(msg.what));
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -688,7 +766,9 @@
                         if (isNrAdvanced()) {
                             transitionTo(mNrConnectedAdvancedState);
                         } else {
-                            transitionTo(mNrConnectedState);
+                            transitionTo(isPhysicalLinkActive()
+                                    || !mFeatureFlags.supportNrSaRrcIdle()
+                                    ? mNrConnectedState : mNrIdleState);
                         }
                     } else if (isLte(rat) && isNrNotRestricted()) {
                         transitionWithTimerTo(isPhysicalLinkActive()
@@ -703,18 +783,19 @@
                     mIsNrRestricted = isNrRestricted();
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
                             if (DBG) log("Reset timers since timer reset is enabled for RRC idle.");
                             resetAllTimers();
+                            updateOverrideNetworkType();
                         }
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
                         if (DBG) log("Reset timers since timer reset is enabled for RRC idle.");
                         resetAllTimers();
@@ -756,10 +837,10 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("IdleState: process " + getEventName(msg.what));
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -768,7 +849,9 @@
                         if (isNrAdvanced()) {
                             transitionTo(mNrConnectedAdvancedState);
                         } else {
-                            transitionTo(mNrConnectedState);
+                            transitionTo(isPhysicalLinkActive()
+                                    || !mFeatureFlags.supportNrSaRrcIdle()
+                                    ? mNrConnectedState : mNrIdleState);
                         }
                     } else if (!isLte(rat) || !isNrNotRestricted()) {
                         transitionWithTimerTo(mLegacyState);
@@ -782,7 +865,8 @@
                     }
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (isPhysicalLinkActive()) {
@@ -794,8 +878,7 @@
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     if (isPhysicalLinkActive()) {
                         transitionWithTimerTo(mLteConnectedState);
                     } else {
@@ -838,10 +921,10 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("LteConnectedState: process " + getEventName(msg.what));
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -850,7 +933,9 @@
                         if (isNrAdvanced()) {
                             transitionTo(mNrConnectedAdvancedState);
                         } else {
-                            transitionTo(mNrConnectedState);
+                            transitionTo(isPhysicalLinkActive()
+                                    || !mFeatureFlags.supportNrSaRrcIdle()
+                                    ? mNrConnectedState : mNrIdleState);
                         }
                     } else if (!isLte(rat) || !isNrNotRestricted()) {
                         transitionWithTimerTo(mLegacyState);
@@ -864,7 +949,8 @@
                     }
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (!isPhysicalLinkActive()) {
@@ -876,8 +962,7 @@
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     if (!isPhysicalLinkActive()) {
                         transitionWithTimerTo(mIdleState);
                     } else {
@@ -903,6 +988,84 @@
     private final LteConnectedState mLteConnectedState = new LteConnectedState();
 
     /**
+     * Device is connected to 5G NR as the primary or secondary cell but not actively using data.
+     */
+    private final class NrIdleState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log("Entering NrIdleState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("NrIdleState: process " + getEventName(msg.what));
+            updateTimers();
+            AsyncResult ar;
+            switch (msg.what) {
+                case EVENT_SERVICE_STATE_CHANGED:
+                    onServiceStateChanged();
+                    // fallthrough
+                case EVENT_UPDATE:
+                    int rat = getDataNetworkType();
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR
+                            || (isLte(rat) && isNrConnected())) {
+                        if (isNrAdvanced()) {
+                            transitionTo(mNrConnectedAdvancedState);
+                        } else if (isPhysicalLinkActive()) {
+                            transitionWithTimerTo(mNrConnectedState);
+                        } else {
+                            // Update in case the override network type changed
+                            updateOverrideNetworkType();
+                        }
+                    } else if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isPhysicalLinkActive()
+                                ? mLteConnectedState : mIdleState);
+                    } else {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
+                        mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
+                    }
+                    // Check NR advanced in case NR advanced bands were added
+                    if (isNrAdvanced()) {
+                        transitionTo(mNrConnectedAdvancedState);
+                    } else if (isPhysicalLinkActive()) {
+                        transitionWithTimerTo(mNrConnectedState);
+                    }
+                    break;
+                case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
+                    mPhysicalLinkStatus = msg.arg1;
+                    if (isPhysicalLinkActive()) {
+                        transitionWithTimerTo(mNrConnectedState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return mFeatureFlags.supportNrSaRrcIdle() ? STATE_CONNECTED_RRC_IDLE : STATE_CONNECTED;
+        }
+    }
+
+    private final NrIdleState mNrIdleState = new NrIdleState();
+
+    /**
      * Device is connected to 5G NR as the primary or secondary cell.
      */
     private final class NrConnectedState extends State {
@@ -920,10 +1083,10 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("NrConnectedState: process " + getEventName(msg.what));
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -931,6 +1094,8 @@
                             || (isLte(rat) && isNrConnected())) {
                         if (isNrAdvanced()) {
                             transitionTo(mNrConnectedAdvancedState);
+                        } else if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
+                            transitionWithTimerTo(mNrIdleState);
                         } else {
                             // Update in case the override network type changed
                             updateOverrideNetworkType();
@@ -943,18 +1108,23 @@
                     }
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
                     // Check NR advanced in case NR advanced bands were added
                     if (isNrAdvanced()) {
                         transitionTo(mNrConnectedAdvancedState);
+                    } else if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
+                        transitionWithTimerTo(mNrIdleState);
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
+                    if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
+                        transitionWithTimerTo(mNrIdleState);
+                    }
                     break;
                 default:
                     return NOT_HANDLED;
@@ -990,12 +1160,16 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) log("NrConnectedAdvancedState: process " + getEventName(msg.what));
+            mLastShownNrDueToAdvancedBand = isAdditionalNrAdvancedBand(mRatchetedNrBands);
+            if (DBG) {
+                log("NrConnectedAdvancedState: process " + getEventName(msg.what)
+                        + ", been using advanced band is " + mLastShownNrDueToAdvancedBand);
+            }
             updateTimers();
+            AsyncResult ar;
             switch (msg.what) {
                 case EVENT_SERVICE_STATE_CHANGED:
-                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
-                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    onServiceStateChanged();
                     // fallthrough
                 case EVENT_UPDATE:
                     int rat = getDataNetworkType();
@@ -1012,7 +1186,9 @@
                                 mOverrideNetworkType =
                                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
                             }
-                            transitionWithTimerTo(mNrConnectedState);
+                            transitionWithTimerTo(isPhysicalLinkActive()
+                                    || !mFeatureFlags.supportNrSaRrcIdle()
+                                    ? mNrConnectedState : mNrIdleState);
                         }
                     } else if (isLte(rat) && isNrNotRestricted()) {
                         transitionWithTimerTo(isPhysicalLinkActive()
@@ -1022,18 +1198,20 @@
                     }
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED:
-                    updatePhysicalChannelConfigs();
+                    ar = (AsyncResult) msg.obj;
+                    updatePhysicalChannelConfigs((List<PhysicalChannelConfig>) ar.result);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
                     // Check NR advanced in case NR advanced bands were removed
                     if (!isNrAdvanced()) {
-                        transitionWithTimerTo(mNrConnectedState);
+                        transitionWithTimerTo(isPhysicalLinkActive()
+                                || !mFeatureFlags.supportNrSaRrcIdle()
+                                ? mNrConnectedState : mNrIdleState);
                     }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    mPhysicalLinkStatus = (int) ar.result;
+                    mPhysicalLinkStatus = msg.arg1;
                     break;
                 default:
                     return NOT_HANDLED;
@@ -1053,14 +1231,26 @@
     private final NrConnectedAdvancedState mNrConnectedAdvancedState =
             new NrConnectedAdvancedState();
 
-    private void updatePhysicalChannelConfigs() {
-        List<PhysicalChannelConfig> physicalChannelConfigs =
-                mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+    /** On service state changed. */
+    private void onServiceStateChanged() {
+        ServiceState ss = mPhone.getServiceStateTracker().getServiceState();
+        if (mIsTimerResetEnabledOnPlmnChanges
+                && !TextUtils.equals(mServiceState.getOperatorNumeric(), ss.getOperatorNumeric())) {
+            log("Reset any timers due to nr_timers_reset_on_plmn_change_bool");
+            resetAllTimers();
+        }
+        mServiceState = ss;
+        if (DBG) log("ServiceState updated: " + mServiceState);
+    }
+
+    private void updatePhysicalChannelConfigs(List<PhysicalChannelConfig> physicalChannelConfigs) {
         boolean isPccListEmpty = physicalChannelConfigs == null || physicalChannelConfigs.isEmpty();
         if (isPccListEmpty && isUsingPhysicalChannelConfigForRrcDetection()) {
             log("Physical channel configs updated: not updating PCC fields for empty PCC list "
                     + "indicating RRC idle.");
+            mPrimaryCellChangedWhileIdle = false;
             mPhysicalChannelConfigs = physicalChannelConfigs;
+            mDoesPccListIndicateIdle = true;
             return;
         }
 
@@ -1107,6 +1297,16 @@
             mRatchetedNrBandwidths = Math.max(mRatchetedNrBandwidths, nrBandwidths);
             mRatchetedNrBands.addAll(nrBands);
         } else {
+            if (mFeatureFlags.supportNrSaRrcIdle() && mDoesPccListIndicateIdle
+                    && isUsingPhysicalChannelConfigForRrcDetection()
+                    && !mPrimaryCellChangedWhileIdle && isTimerActiveForRrcIdle()
+                    && !isNrAdvancedForPccFields(nrBandwidths, nrBands)) {
+                log("Allow primary cell change during RRC idle timer without changing state: "
+                        + mLastAnchorNrCellId + " -> " + anchorNrCellId);
+                mPrimaryCellChangedWhileIdle = true;
+                mLastAnchorNrCellId = anchorNrCellId;
+                return;
+            }
             if (mRatchetPccFieldsForSameAnchorNrCell) {
                 log("Not ratcheting physical channel config fields since anchor NR cell changed: "
                         + mLastAnchorNrCellId + " -> " + anchorNrCellId);
@@ -1117,6 +1317,7 @@
 
         mLastAnchorNrCellId = anchorNrCellId;
         mPhysicalChannelConfigs = physicalChannelConfigs;
+        mDoesPccListIndicateIdle = false;
         if (DBG) {
             log("Physical channel configs updated: anchorNrCell=" + mLastAnchorNrCellId
                     + ", nrBandwidths=" + mRatchetedNrBandwidths + ", nrBands=" +  mRatchetedNrBands
@@ -1148,6 +1349,10 @@
         }
         if (!mIsDeviceIdleMode && rule != null && rule.getSecondaryTimer(currentName) > 0) {
             int duration = rule.getSecondaryTimer(currentName);
+            if (mLastShownNrDueToAdvancedBand && mNrAdvancedBandsSecondaryTimer > 0) {
+                duration = mNrAdvancedBandsSecondaryTimer;
+                if (DBG) log("timer adjusted by nr_advanced_bands_secondary_timer_seconds_int");
+            }
             if (DBG) log(duration + "s secondary timer started for state: " + currentName);
             mSecondaryTimerState = currentName;
             mPreviousState = currentName;
@@ -1165,7 +1370,11 @@
             if (isNrAdvanced()) {
                 transitionState = mNrConnectedAdvancedState;
             } else {
-                transitionState = mNrConnectedState;
+                if (isPhysicalLinkActive() || !mFeatureFlags.supportNrSaRrcIdle()) {
+                    transitionState = mNrConnectedState;
+                } else {
+                    transitionState = mNrIdleState;
+                }
             }
         } else if (isLte(dataRat) && isNrNotRestricted()) {
             if (isPhysicalLinkActive()) {
@@ -1194,7 +1403,9 @@
 
         String currentState = getCurrentState().getName();
 
-        if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType()) {
+        if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType()
+                && getDataNetworkType()
+                == mDisplayInfoController.getTelephonyDisplayInfo().getNetworkType()) {
             // remove primary timer if device goes back to the original icon
             if (DBG) {
                 log("Remove primary timer since icon of primary state and current icon equal: "
@@ -1246,6 +1457,20 @@
         mIsSecondaryTimerActive = false;
         mPrimaryTimerState = "";
         mSecondaryTimerState = "";
+
+        mLastShownNrDueToAdvancedBand = false;
+    }
+
+    private boolean isTimerActiveForRrcIdle() {
+        if (mIsPrimaryTimerActive) {
+            return mPrimaryTimerState.equals(STATE_CONNECTED_RRC_IDLE)
+                    || mPrimaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE);
+        } else if (mIsSecondaryTimerActive) {
+            return mSecondaryTimerState.equals(STATE_CONNECTED_RRC_IDLE)
+                    || mSecondaryTimerState.equals(STATE_NOT_RESTRICTED_RRC_IDLE);
+        } else {
+            return false;
+        }
     }
 
     /**
@@ -1320,6 +1545,16 @@
             return secondaryTimer == null ? 0 : secondaryTimer;
         }
 
+        /**
+         * @return Whether timer rules have been defined for this {@link #mState}.
+         */
+        public boolean isDefined() {
+            // TODO: Remove this method added to make STATE_CONNECTED_RRC_IDLE backwards compatible
+            //  with STATE_CONNECTED once carrier configs are updated.
+            return mOverrideType != TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+                    || !mPrimaryTimers.isEmpty() || !mSecondaryTimers.isEmpty();
+        }
+
         @Override
         public String toString() {
             return "{mState=" + mState
@@ -1346,6 +1581,10 @@
      * @return {@code true} if the device is in NR advanced mode (i.e. 5G+).
      */
     private boolean isNrAdvanced() {
+        return isNrAdvancedForPccFields(mRatchetedNrBandwidths, mRatchetedNrBands);
+    }
+
+    private boolean isNrAdvancedForPccFields(int bandwidths, Set<Integer> bands) {
         // Check PCO requirement. For carriers using PCO to indicate whether the data connection is
         // NR advanced capable, mNrAdvancedCapablePcoId should be configured to non-zero.
         if (mNrAdvancedCapablePcoId > 0 && !mIsNrAdvancedAllowedByPco) {
@@ -1362,10 +1601,9 @@
 
         // Check if meeting minimum bandwidth requirement. For most carriers, there is no minimum
         // bandwidth requirement and mNrAdvancedThresholdBandwidth is 0.
-        if (mNrAdvancedThresholdBandwidth > 0
-                && mRatchetedNrBandwidths < mNrAdvancedThresholdBandwidth) {
+        if (mNrAdvancedThresholdBandwidth > 0 && bandwidths < mNrAdvancedThresholdBandwidth) {
             if (DBG) {
-                log("isNrAdvanced: false because bandwidths=" + mRatchetedNrBandwidths
+                log("isNrAdvanced: false because bandwidths=" + bandwidths
                         + " does not meet the threshold=" + mNrAdvancedThresholdBandwidth);
             }
             return false;
@@ -1373,24 +1611,24 @@
 
         // If all above tests passed, then check if the device is using millimeter wave bands or
         // carrier designated bands.
-        return isNrMmwave() || isAdditionalNrAdvancedBand();
+        return isNrMmwave() || isAdditionalNrAdvancedBand(bands);
     }
 
     private boolean isNrMmwave() {
         return mServiceState.getNrFrequencyRange() == ServiceState.FREQUENCY_RANGE_MMWAVE;
     }
 
-    private boolean isAdditionalNrAdvancedBand() {
-        if (mAdditionalNrAdvancedBands.isEmpty() || mRatchetedNrBands.isEmpty()) {
+    private boolean isAdditionalNrAdvancedBand(Set<Integer> bands) {
+        if (mAdditionalNrAdvancedBands.isEmpty() || bands.isEmpty()) {
             if (DBG && !mAdditionalNrAdvancedBands.isEmpty()) {
                 // Only log if mAdditionalNrAdvancedBands is empty to prevent log spam
                 log("isAdditionalNrAdvancedBand: false because bands are empty; configs="
-                        + mAdditionalNrAdvancedBands + ", bands=" + mRatchetedNrBands);
+                        + mAdditionalNrAdvancedBands + ", bands=" + bands);
             }
             return false;
         }
         Set<Integer> intersection = new HashSet<>(mAdditionalNrAdvancedBands);
-        intersection.retainAll(mRatchetedNrBands);
+        intersection.retainAll(bands);
         return !intersection.isEmpty();
     }
 
@@ -1404,7 +1642,8 @@
     }
 
     private int getPhysicalLinkStatusFromPhysicalChannelConfig() {
-        return (mPhysicalChannelConfigs == null || mPhysicalChannelConfigs.isEmpty())
+        return (mPhysicalChannelConfigs == null || mPhysicalChannelConfigs.isEmpty()
+                || mDoesPccListIndicateIdle)
                 ? DataCallResponse.LINK_STATUS_DORMANT : DataCallResponse.LINK_STATUS_ACTIVE;
     }
 
@@ -1449,6 +1688,7 @@
         pw.flush();
         pw.increaseIndent();
         pw.println("mSubId=" + mPhone.getSubId());
+        pw.println("supportNrSaRrcIdle=" + mFeatureFlags.supportNrSaRrcIdle());
         pw.println("mOverrideTimerRules=" + mOverrideTimerRules.toString());
         pw.println("mLteEnhancedPattern=" + mLteEnhancedPattern);
         pw.println("mIsPhysicalChannelConfigOn=" + mIsPhysicalChannelConfigOn);
@@ -1465,6 +1705,7 @@
         pw.println("mAdditionalNrAdvancedBandsList=" + mAdditionalNrAdvancedBands);
         pw.println("mRatchetedNrBands=" + mRatchetedNrBands);
         pw.println("mLastAnchorNrCellId=" + mLastAnchorNrCellId);
+        pw.println("mDoesPccListIndicateIdle=" + mDoesPccListIndicateIdle);
         pw.println("mPrimaryTimerState=" + mPrimaryTimerState);
         pw.println("mSecondaryTimerState=" + mSecondaryTimerState);
         pw.println("mPreviousState=" + mPreviousState);
@@ -1473,6 +1714,7 @@
         pw.println("mIsNrAdvancedAllowedByPco=" + mIsNrAdvancedAllowedByPco);
         pw.println("mNrAdvancedCapablePcoId=" + mNrAdvancedCapablePcoId);
         pw.println("mIsUsingUserDataForRrcDetection=" + mIsUsingUserDataForRrcDetection);
+        pw.println("mPrimaryCellChangedWhileIdle=" + mPrimaryCellChangedWhileIdle);
         pw.println("mEnableNrAdvancedWhileRoaming=" + mEnableNrAdvancedWhileRoaming);
         pw.println("mIsDeviceIdleMode=" + mIsDeviceIdleMode);
         pw.decreaseIndent();
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 0f4f528..97eb447 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.hardware.radio.modem.ImeiInfo;
 import android.net.Uri;
@@ -72,7 +73,6 @@
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.telephony.satellite.SatelliteDatagram;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
@@ -84,6 +84,7 @@
 import com.android.ims.ImsManager;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.analytics.TelephonyAnalytics;
 import com.android.internal.telephony.data.AccessNetworksManager;
 import com.android.internal.telephony.data.DataNetworkController;
 import com.android.internal.telephony.data.DataSettingsManager;
@@ -92,6 +93,7 @@
 import com.android.internal.telephony.emergency.EmergencyConstants;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsCallInfo;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
@@ -219,7 +221,6 @@
     private static final int EVENT_UNSOL_OEM_HOOK_RAW               = 34;
     protected static final int EVENT_GET_RADIO_CAPABILITY           = 35;
     protected static final int EVENT_SS                             = 36;
-    private static final int EVENT_CONFIG_LCE                       = 37;
     private static final int EVENT_CHECK_FOR_NETWORK_AUTOMATIC      = 38;
     protected static final int EVENT_VOICE_RADIO_TECH_CHANGED       = 39;
     protected static final int EVENT_REQUEST_VOICE_RADIO_TECH_DONE  = 40;
@@ -251,8 +252,14 @@
     protected static final int EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE = 66;
     protected static final int EVENT_GET_DEVICE_IMEI_DONE = 67;
     protected static final int EVENT_TRIGGER_NOTIFY_ANBR = 68;
-
-    protected static final int EVENT_LAST = EVENT_TRIGGER_NOTIFY_ANBR;
+    protected static final int EVENT_GET_N1_MODE_ENABLED_DONE = 69;
+    protected static final int EVENT_SET_N1_MODE_ENABLED_DONE = 70;
+    protected static final int EVENT_IMEI_MAPPING_CHANGED = 71;
+    protected static final int EVENT_CELL_IDENTIFIER_DISCLOSURE = 72;
+    protected static final int EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE = 73;
+    protected static final int EVENT_SECURITY_ALGORITHM_UPDATE = 74;
+    protected static final int EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE = 75;
+    protected static final int EVENT_LAST = EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE;
 
     // For shared prefs.
     private static final String GSM_ROAMING_LIST_OVERRIDE_PREFIX = "gsm_roaming_list_";
@@ -283,6 +290,14 @@
             "pref_null_cipher_and_integrity_enabled";
     private final TelephonyAdminReceiver m2gAdminUpdater;
 
+    public static final String PREF_IDENTIFIER_DISCLOSURE_NOTIFICATIONS_ENABLED =
+            "pref_identifier_disclosure_notifications_enabled";
+
+    public static final String PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED =
+            "pref_null_cipher_notifications_enabled";
+
+    protected final FeatureFlags mFeatureFlags;
+
     /**
      * This method is invoked when the Phone exits Emergency Callback Mode.
      */
@@ -371,9 +386,6 @@
     private final AtomicReference<RadioCapability> mRadioCapability =
             new AtomicReference<RadioCapability>();
 
-    private static final int DEFAULT_REPORT_INTERVAL_MS = 200;
-    private static final boolean LCE_PULL_MODE = true;
-    private int mLceStatus = RILConstants.LCE_NOT_AVAILABLE;
     protected TelephonyComponentFactory mTelephonyComponentFactory;
 
     private int mPreferredUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
@@ -480,6 +492,7 @@
 
     protected VoiceCallSessionStats mVoiceCallSessionStats;
     protected SmsStats mSmsStats;
+    protected TelephonyAnalytics mTelephonyAnalytics;
 
     protected LinkBandwidthEstimator mLinkBandwidthEstimator;
 
@@ -550,22 +563,25 @@
     /**
      * Constructs a Phone in normal (non-unit test) mode.
      *
+     * @param name a name for this phone object
      * @param notifier An instance of DefaultPhoneNotifier,
      * @param context Context object from hosting application
      * unless unit testing.
      * @param ci is CommandsInterface
      * @param unitTestMode when true, prevents notifications
      * of state change events
+     * @param featureFlags an instance of the FeatureFlags set
      */
     protected Phone(String name, PhoneNotifier notifier, Context context, CommandsInterface ci,
-                    boolean unitTestMode) {
+                    boolean unitTestMode, FeatureFlags featureFlags) {
         this(name, notifier, context, ci, unitTestMode, SubscriptionManager.DEFAULT_PHONE_INDEX,
-                TelephonyComponentFactory.getInstance());
+                TelephonyComponentFactory.getInstance(), featureFlags);
     }
 
     /**
      * Constructs a Phone in normal (non-unit test) mode.
      *
+     * @param name a name for this phone object
      * @param notifier An instance of DefaultPhoneNotifier,
      * @param context Context object from hosting application
      * unless unit testing.
@@ -573,10 +589,13 @@
      * @param unitTestMode when true, prevents notifications
      * of state change events
      * @param phoneId the phone-id of this phone.
+     * @param telephonyComponentFactory a factory for injecting telephony components
+     * @param featureFlags an instance of the FeatureFlags set
      */
     protected Phone(String name, PhoneNotifier notifier, Context context, CommandsInterface ci,
                     boolean unitTestMode, int phoneId,
-                    TelephonyComponentFactory telephonyComponentFactory) {
+                    TelephonyComponentFactory telephonyComponentFactory,
+                    FeatureFlags featureFlags) {
         mPhoneId = phoneId;
         mName = name;
         mNotifier = notifier;
@@ -589,6 +608,8 @@
                 .makeAppSmsManager(context);
         mLocalLog = new LocalLog(64);
 
+        mFeatureFlags = featureFlags;
+
         setUnitTestMode(unitTestMode);
 
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@@ -649,8 +670,10 @@
         if (getPhoneType() != PhoneConstants.PHONE_TYPE_SIP) {
             mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
         }
-        mCi.startLceService(DEFAULT_REPORT_INTERVAL_MS, LCE_PULL_MODE,
-                obtainMessage(EVENT_CONFIG_LCE));
+        //Initialize Telephony Analytics
+        if (mFeatureFlags.enableTelephonyAnalytics()) {
+            mTelephonyAnalytics = new TelephonyAnalytics(this);
+        }
     }
 
     /**
@@ -830,16 +853,6 @@
                 // deprecated, ignore
                 break;
 
-            case EVENT_CONFIG_LCE:
-                ar = (AsyncResult) msg.obj;
-                if (ar.exception != null) {
-                    Rlog.d(LOG_TAG, "config LCE service failed: " + ar.exception);
-                } else {
-                    final ArrayList<Integer> statusInfo = (ArrayList<Integer>)ar.result;
-                    mLceStatus = statusInfo.get(0);
-                }
-                break;
-
             case EVENT_CHECK_FOR_NETWORK_AUTOMATIC: {
                 onCheckForNetworkSelectionModeAutomatic(msg);
                 break;
@@ -889,7 +902,7 @@
                 }
                 break;
             default:
-                throw new RuntimeException("unexpected event not handled");
+                throw new RuntimeException("unexpected event not handled, msgId=" + msg.what);
         }
     }
 
@@ -1071,9 +1084,20 @@
     public void notifySmsSent(String destinationAddress) {
         TelephonyManager m = (TelephonyManager) getContext().getSystemService(
                 Context.TELEPHONY_SERVICE);
-        if (m != null && m.isEmergencyNumber(destinationAddress)) {
-            mLocalLog.log("Emergency SMS detected, recording time.");
-            mTimeLastEmergencySmsSentMs = SystemClock.elapsedRealtime();
+        if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()) {
+            if (m != null && m.isEmergencyNumber(destinationAddress)) {
+                mLocalLog.log("Emergency SMS detected, recording time.");
+                mTimeLastEmergencySmsSentMs = SystemClock.elapsedRealtime();
+            }
+        } else {
+            if (mContext.getPackageManager() != null
+                    && mContext.getPackageManager().hasSystemFeature(
+                            PackageManager.FEATURE_TELEPHONY_CALLING)) {
+                if (m != null && m.isEmergencyNumber(destinationAddress)) {
+                    mLocalLog.log("Emergency SMS detected, recording time.");
+                    mTimeLastEmergencySmsSentMs = SystemClock.elapsedRealtime();
+                }
+            }
         }
     }
 
@@ -2743,47 +2767,6 @@
     }
 
     /**
-     * Invokes RIL_REQUEST_OEM_HOOK_RAW on RIL implementation.
-     *
-     * @param data The data for the request.
-     * @param response <strong>On success</strong>,
-     * (byte[])(((AsyncResult)response.obj).result)
-     * <strong>On failure</strong>,
-     * (((AsyncResult)response.obj).result) == null and
-     * (((AsyncResult)response.obj).exception) being an instance of
-     * com.android.internal.telephony.gsm.CommandException
-     *
-     * @see #invokeOemRilRequestRaw(byte[], android.os.Message)
-     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
-     */
-    @UnsupportedAppUsage
-    @Deprecated
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-        mCi.invokeOemRilRequestRaw(data, response);
-    }
-
-    /**
-     * Invokes RIL_REQUEST_OEM_HOOK_Strings on RIL implementation.
-     *
-     * @param strings The strings to make available as the request data.
-     * @param response <strong>On success</strong>, "response" bytes is
-     * made available as:
-     * (String[])(((AsyncResult)response.obj).result).
-     * <strong>On failure</strong>,
-     * (((AsyncResult)response.obj).result) == null and
-     * (((AsyncResult)response.obj).exception) being an instance of
-     * com.android.internal.telephony.gsm.CommandException
-     *
-     * @see #invokeOemRilRequestStrings(java.lang.String[], android.os.Message)
-     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
-     */
-    @UnsupportedAppUsage
-    @Deprecated
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-        mCi.invokeOemRilRequestStrings(strings, response);
-    }
-
-    /**
      * Read one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
      * Used for device configuration by some CDMA operators.
      *
@@ -3783,7 +3766,7 @@
      * @return {@code true} if internet data is allowed to be established.
      */
     public boolean isDataAllowed() {
-        return getDataNetworkController().isInternetDataAllowed();
+        return getDataNetworkController().isInternetDataAllowed(false/* ignoreExistingNetworks */);
     }
 
     /**
@@ -4439,12 +4422,15 @@
             subInfo = subInfoInternal.toSubscriptionInfo();
         }
 
-        if (subInfo == null
-                || subInfo.getUsageSetting() == SubscriptionManager.USAGE_SETTING_UNKNOWN) {
+        if (subInfo == null) {
             loge("Failed to get SubscriptionInfo for subId=" + subId);
             return SubscriptionManager.USAGE_SETTING_UNKNOWN;
         }
 
+        if (subInfo.getUsageSetting() == SubscriptionManager.USAGE_SETTING_UNKNOWN) {
+            return SubscriptionManager.USAGE_SETTING_UNKNOWN;
+        }
+
         if (subInfo.getUsageSetting() != SubscriptionManager.USAGE_SETTING_DEFAULT) {
             return subInfo.getUsageSetting();
         }
@@ -4546,13 +4532,6 @@
     }
 
     /**
-     * Returns the status of Link Capacity Estimation (LCE) service.
-     */
-    public int getLceStatus() {
-        return mLceStatus;
-    }
-
-    /**
      * Returns the modem activity information
      */
     public void getModemActivityInfo(Message response, WorkSource workSource)  {
@@ -4560,15 +4539,6 @@
     }
 
     /**
-     * Starts LCE service after radio becomes available.
-     * LCE service state may get destroyed on the modem when radio becomes unavailable.
-     */
-    public void startLceAfterRadioIsAvailable() {
-        mCi.startLceService(DEFAULT_REPORT_INTERVAL_MS, LCE_PULL_MODE,
-                obtainMessage(EVENT_CONFIG_LCE));
-    }
-
-    /**
      * Control the data throttling at modem.
      *
      * @param result Message that will be sent back to the requester
@@ -4827,6 +4797,11 @@
         mSmsStats = smsStats;
     }
 
+    /** Getter for Telephony Analytics */
+    public TelephonyAnalytics getTelephonyAnalytics() {
+        return mTelephonyAnalytics;
+    }
+
     /** @hide */
     public CarrierPrivilegesTracker getCarrierPrivilegesTracker() {
         return null;
@@ -5195,6 +5170,56 @@
     }
 
     /**
+     * @return whether or not this Phone interacts with a modem that supports the cellular
+     * identifier disclosure transparency feature.
+     */
+    public boolean isIdentifierDisclosureTransparencySupported() {
+        return false;
+    }
+
+    /**
+     * @return global cellular identifier disclosure transparency enabled preference
+     */
+    public boolean getIdentifierDisclosureNotificationsPreferenceEnabled() {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+        return sp.getBoolean(PREF_IDENTIFIER_DISCLOSURE_NOTIFICATIONS_ENABLED, false);
+    }
+
+    /**
+     * Override to handle an update to the cellular identifier disclosure transparency preference.
+     */
+    public void handleIdentifierDisclosureNotificationPreferenceChange() {
+    }
+
+    /**
+     * @return whether this Phone interacts with a modem that supports the null cipher
+     * notification feature.
+     */
+    public boolean isNullCipherNotificationSupported() {
+        return false;
+    }
+
+    /**
+     * @return whether the global null cipher notifications preference is enabled.
+     */
+    public boolean getNullCipherNotificationsPreferenceEnabled() {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+        return sp.getBoolean(PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED, false);
+    }
+
+    /**
+     * Override to handle an update to the null cipher notification preference.
+     */
+    public void handleNullCipherNotificationPreferenceChanged() {
+    }
+
+    /**
+     * Refresh the safety sources in response to the identified broadcast.
+     */
+    public void refreshSafetySources(String refreshBroadcastId) {
+    }
+
+    /**
      * Notifies the IMS call status to the modem.
      *
      * @param imsCallInfo The list of {@link ImsCallInfo}.
@@ -5238,279 +5263,6 @@
     }
 
     /**
-     * Start receiving satellite position updates.
-     * This can be called by the pointing UI when the user starts pointing to the satellite.
-     * Modem should continue to report the pointing input as the device or satellite moves.
-     *
-     * @param result The Message to send to result of the operation to.
-     **/
-    public void startSatellitePositionUpdates(Message result) {
-        mCi.startSendingSatellitePointingInfo(result);
-    }
-
-    /**
-     * Stop receiving satellite position updates.
-     * This can be called by the pointing UI when the user stops pointing to the satellite.
-     *
-     * @param result The Message to send to result of the operation to.
-     **/
-    public void stopSatellitePositionUpdates(Message result) {
-        mCi.stopSendingSatellitePointingInfo(result);
-    }
-
-    /**
-     * Get maximum number of characters per text message on satellite.
-     * @param result The Message to send the result of the operation to.
-     */
-    public void getMaxCharactersPerSatelliteTextMessage(Message result) {
-        mCi.getMaxCharactersPerSatelliteTextMessage(result);
-    }
-
-    /**
-     * Power on or off the satellite modem.
-     * @param result The Message to send the result of the operation to.
-     * @param powerOn {@code true} to power on the satellite modem and {@code false} to power off.
-     */
-    public void setSatellitePower(Message result, boolean powerOn) {
-        mCi.setSatellitePower(result, powerOn);
-    }
-
-    /**
-     * Check whether the satellite modem is powered on.
-     * @param result The Message to send the result of the operation to.
-     */
-    public void isSatellitePowerOn(Message result) {
-        mCi.getSatellitePowerState(result);
-    }
-
-    /**
-     * Check whether the satellite service is supported on the device.
-     * @param result The Message to send the result of the operation to.
-     */
-    public void isSatelliteSupported(Message result) {
-        mCi.isSatelliteSupported(result);
-    }
-
-    /**
-     * Check whether the satellite modem is provisioned.
-     * @param result The Message to send the result of the operation to.
-     */
-    public void isSatelliteProvisioned(Message result) {
-        mCi.getSatelliteProvisionState(result);
-    }
-
-    /**
-     * Get the satellite capabilities.
-     * @param result The Message to send the result of the operation to.
-     */
-    public void getSatelliteCapabilities(Message result) {
-        mCi.getSatelliteCapabilities(result);
-    }
-
-    /**
-     * Registers for pointing info changed from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    public void registerForSatellitePositionInfoChanged(@NonNull Handler h,
-            int what, @Nullable Object obj) {
-        //TODO: Rename CommandsInterface and other modules when updating HAL APIs.
-        mCi.registerForSatellitePointingInfoChanged(h, what, obj);
-    }
-
-    /**
-     * Unregisters for pointing info changed from satellite modem.
-     *
-     * @param h Handler to be removed from the registrant list.
-     */
-    public void unregisterForSatellitePositionInfoChanged(@NonNull Handler h) {
-        //TODO: Rename CommandsInterface and other modules when updating HAL APIs.
-        mCi.unregisterForSatellitePointingInfoChanged(h);
-    }
-
-    /**
-     * Registers for datagrams delivered events from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    public void registerForSatelliteDatagramsDelivered(@NonNull Handler h,
-            int what, @Nullable Object obj) {
-        //TODO: Remove.
-        mCi.registerForSatelliteMessagesTransferComplete(h, what, obj);
-    }
-
-    /**
-     * Unregisters for datagrams delivered events from satellite modem.
-     *
-     * @param h Handler to be removed from the registrant list.
-     */
-    public void unregisterForSatelliteDatagramsDelivered(@NonNull Handler h) {
-        //TODO: Remove.
-        mCi.unregisterForSatelliteMessagesTransferComplete(h);
-    }
-
-    /**
-     * Provision the subscription with a satellite provider.
-     * This is needed to register the device/subscription if the provider allows dynamic
-     * registration.
-     *
-     * @param result Callback message to receive the result.
-     * @param token The token of the device/subscription to be provisioned.
-     */
-    public void provisionSatelliteService(Message result, String token) {
-        // TODO: update parameters in HAL
-        // mCi.provisionSatelliteService(result, token);
-    }
-
-    /**
-     * Deprovision the device/subscription with a satellite provider.
-     * This is needed to unregister the device/subscription if the provider allows dynamic
-     * registration.
-     * If provisioning is in progress for the given SIM, cancel the request.
-     * If there is no request in progress, deprovision the given SIM.
-     *
-     * @param result Callback message to receive the result.
-     * @param token The token of the device/subscription to be deprovisioned.
-     */
-    public void deprovisionSatelliteService(Message result, String token) {
-        //TODO (b/266126070): add implementation.
-    }
-
-    /**
-     * Register for a satellite provision state changed event.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    public void registerForSatelliteProvisionStateChanged(Handler h, int what, Object obj) {
-        mCi.registerForSatelliteProvisionStateChanged(h, what, obj);
-    }
-
-    /**
-     * Unregister for a satellite provision state changed event.
-     *
-     * @param h Handler to be removed from the registrant list.
-     */
-    public void unregisterForSatelliteProvisionStateChanged(Handler h) {
-        mCi.unregisterForSatelliteProvisionStateChanged(h);
-    }
-
-    /**
-     * Get the list of provisioned satellite features.
-     *
-     * @param result Callback message to receive the result.
-     */
-    public void getProvisionedSatelliteFeatures(Message result) {
-        //TODO (b/266126070): add implementation.
-    }
-
-    /**
-     * Registers for satellite state changed from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    public void registerForSatelliteModemStateChanged(@NonNull Handler h, int what,
-            @Nullable Object obj) {
-        mCi.registerForSatelliteModeChanged(h, what, obj);
-    }
-
-    /**
-     * Unregisters for satellite state changed from satellite modem.
-     *
-     * @param h Handler to be removed from registrant list.
-     */
-    public void unregisterForSatelliteModemStateChanged(@NonNull Handler h) {
-        mCi.unregisterForSatelliteModeChanged(h);
-    }
-
-    /**
-     * Registers for pending datagram count info from satellite modem.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    public void registerForPendingDatagramCount(@NonNull Handler h, int what,
-            @Nullable Object obj) {
-        mCi.registerForPendingSatelliteMessageCount(h, what, obj);
-    }
-
-    /**
-     * Unregisters for pending datagram count info from satellite modem.
-     *
-     * @param h Handler to be removed from registrant list.
-     */
-    public void unregisterForPendingDatagramCount(@NonNull Handler h) {
-        mCi.unregisterForPendingSatelliteMessageCount(h);
-    }
-
-    /**
-     * Register to receive incoming datagrams over satellite.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    public void registerForSatelliteDatagramsReceived(@NonNull Handler h, int what,
-            @Nullable Object obj) {
-        // TODO: rename
-        mCi.registerForNewSatelliteMessages(h, what, obj);
-    }
-
-    /**
-     * Unregister to stop receiving incoming datagrams over satellite.
-     *
-     * @param h Handler to be removed from registrant list.
-     */
-    public void unregisterForSatelliteDatagramsReceived(@NonNull Handler h) {
-        // TODO: rename
-        mCi.unregisterForNewSatelliteMessages(h);
-    }
-
-    /**
-     * Poll pending datagrams over satellite.
-     * @param result The Message to send the result of the operation to.
-     */
-    public void pollPendingSatelliteDatagrams(Message result) {
-        //mCi.pollPendingSatelliteDatagrams(result);
-    }
-
-    /**
-     * Send datagram over satellite.
-     * @param result The Message to send the result of the operation to.
-     * @param datagram Datagram to send over satellite.
-     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
-     *                                 full screen mode.
-     */
-    public void sendSatelliteDatagram(Message result, SatelliteDatagram datagram,
-            boolean needFullScreenPointingUI) {
-        //mCi.sendSatelliteDatagram(result, datagram);
-    }
-
-    /**
-     * Check whether satellite communication is allowed for the current location.
-     * @param result The Message to send the result of the operation to.
-     */
-    public void isSatelliteCommunicationAllowedForCurrentLocation(Message result) {
-        mCi.isSatelliteCommunicationAllowedForCurrentLocation(result);
-    }
-
-    /**
-     * Get the time after which the satellite will be visible.
-     * @param result The Message to send the result of the operation to.
-     */
-    public void requestTimeForNextSatelliteVisibility(Message result) {
-        mCi.getTimeForNextSatelliteVisibility(result);
-    }
-
-    /**
      * Start callback mode
      * @param type for callback mode entry.
      */
@@ -5727,6 +5479,13 @@
             pw.flush();
             pw.println("++++++++++++++++++++++++++++++++");
         }
+        if (mTelephonyAnalytics != null) {
+            try {
+                mTelephonyAnalytics.dump(fd, pw, args);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
     }
 
     private void logd(String s) {
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 06ab584..7141f37 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -19,6 +19,7 @@
 import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
 import static android.telephony.TelephonyManager.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncResult;
@@ -33,16 +34,23 @@
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.telephony.Rlog;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * This class manages phone's configuration which defines the potential capability (static) of the
@@ -63,10 +71,17 @@
     private static final int EVENT_GET_MODEM_STATUS_DONE = 102;
     private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103;
     private static final int EVENT_DEVICE_CONFIG_CHANGED = 104;
+    private static final int EVENT_GET_SIMULTANEOUS_CALLING_SUPPORT_DONE = 105;
+    private static final int EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED = 106;
+
 
     private static PhoneConfigurationManager sInstance = null;
     private final Context mContext;
     private PhoneCapability mStaticCapability;
+    private final Set<Integer> mSlotsSupportingSimultaneousCellularCalls = new HashSet<>(3);
+    private final Set<Integer> mSubIdsSupportingSimultaneousCellularCalls = new HashSet<>(3);
+    private final HashSet<Consumer<Set<Integer>>> mSimultaneousCellularCallingListeners =
+            new HashSet<>(1);
     private final RadioConfig mRadioConfig;
     private final Handler mHandler;
     // mPhones is obtained from PhoneFactory and can have phones corresponding to inactive modems as
@@ -75,6 +90,11 @@
     private final Map<Integer, Boolean> mPhoneStatusMap;
     private MockableInterface mMi = new MockableInterface();
     private TelephonyManager mTelephonyManager;
+
+    /** Feature flags */
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+    private final DefaultPhoneNotifier mNotifier;
     /**
      * True if 'Virtual DSDA' i.e., in-call IMS connectivity on both subs with only single logical
      * modem, is enabled.
@@ -88,10 +108,11 @@
      * Init method to instantiate the object
      * Should only be called once.
      */
-    public static PhoneConfigurationManager init(Context context) {
+    public static PhoneConfigurationManager init(Context context,
+            @NonNull FeatureFlags featureFlags) {
         synchronized (PhoneConfigurationManager.class) {
             if (sInstance == null) {
-                sInstance = new PhoneConfigurationManager(context);
+                sInstance = new PhoneConfigurationManager(context, featureFlags);
             } else {
                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
             }
@@ -103,8 +124,9 @@
      * Constructor.
      * @param context context needed to send broadcast.
      */
-    private PhoneConfigurationManager(Context context) {
+    private PhoneConfigurationManager(Context context, @NonNull FeatureFlags featureFlags) {
         mContext = context;
+        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
@@ -114,6 +136,7 @@
         mPhoneStatusMap = new HashMap<>();
         mVirtualDsdaEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_VIRTUAL_DSDA, false);
+        mNotifier = new DefaultPhoneNotifier(mContext, mFeatureFlags);
         DeviceConfig.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_TELEPHONY, Runnable::run,
                 properties -> {
@@ -132,6 +155,25 @@
         }
     }
 
+    /**
+     * Updates the mapping between the slot IDs that support simultaneous calling and the
+     * associated sub IDs as well as notifies listeners.
+     */
+    private void updateSimultaneousSubIdsFromPhoneIdMappingAndNotify() {
+        if (!mFeatureFlags.simultaneousCallingIndications()) return;
+        Set<Integer> slotCandidates = mSlotsSupportingSimultaneousCellularCalls.stream()
+                .map(i -> mPhones[i].getSubId())
+                .filter(i ->i > SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                        .collect(Collectors.toSet());
+        if (mSubIdsSupportingSimultaneousCellularCalls.equals(slotCandidates))  return;
+        log("updateSimultaneousSubIdsFromPhoneIdMapping update: "
+                + mSubIdsSupportingSimultaneousCellularCalls + " -> " + slotCandidates);
+        mSubIdsSupportingSimultaneousCellularCalls.clear();
+        mSubIdsSupportingSimultaneousCellularCalls.addAll(slotCandidates);
+        mNotifier.notifySimultaneousCellularCallingSubscriptionsChanged(
+                mSubIdsSupportingSimultaneousCellularCalls);
+    }
+
     private void registerForRadioState(Phone phone) {
         phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone);
     }
@@ -144,18 +186,66 @@
         }
     }
 
-    // If virtual DSDA is enabled for this UE, then updates maxActiveVoiceSubscriptions to 2.
-    private PhoneCapability maybeUpdateMaxActiveVoiceSubscriptions(
+    /**
+     * If virtual DSDA is enabled for this UE, then increase maxActiveVoiceSubscriptions to 2.
+     */
+    private PhoneCapability maybeOverrideMaxActiveVoiceSubscriptions(
             final PhoneCapability staticCapability) {
-        if (staticCapability.getLogicalModemList().size() > 1 && mVirtualDsdaEnabled) {
+        boolean isVDsdaEnabled = staticCapability.getLogicalModemList().size() > 1
+                && mVirtualDsdaEnabled;
+        boolean isBkwdCompatDsdaEnabled = mFeatureFlags.simultaneousCallingIndications()
+                && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA);
+        if (isVDsdaEnabled || isBkwdCompatDsdaEnabled) {
+            // Since we already initialized maxActiveVoiceSubscriptions to the count the
+            // modem is capable of, we are only able to increase that count via this method. We do
+            // not allow a decrease of maxActiveVoiceSubscriptions:
+            int updatedMaxActiveVoiceSubscriptions =
+                    Math.max(staticCapability.getMaxActiveVoiceSubscriptions(), 2);
             return new PhoneCapability.Builder(staticCapability)
-                    .setMaxActiveVoiceSubscriptions(2)
+                    .setMaxActiveVoiceSubscriptions(updatedMaxActiveVoiceSubscriptions)
                     .build();
         } else {
             return staticCapability;
         }
     }
 
+    private void maybeEnableCellularDSDASupport() {
+        boolean bkwdsCompatDsda = mFeatureFlags.simultaneousCallingIndications()
+                && getPhoneCount() > 1
+                && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA);
+        boolean halSupportSimulCalling = mRadioConfig != null
+                && mRadioConfig.getRadioConfigProxy(null).getVersion().greaterOrEqual(
+                        RIL.RADIO_HAL_VERSION_2_2)
+                && getPhoneCount() > 1 && mStaticCapability.getMaxActiveVoiceSubscriptions() > 1;
+        // Register for simultaneous calling support changes in the modem if the HAL supports it
+        if (halSupportSimulCalling) {
+            updateSimultaneousCallingSupport();
+            mRadioConfig.registerForSimultaneousCallingSupportStatusChanged(mHandler,
+                    EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED, null);
+        } else if (bkwdsCompatDsda) {
+            // For older devices that only declare that they support DSDA via modem config,
+            // set DSDA as capable now statically.
+            log("DSDA modem config detected - setting DSDA enabled");
+            for (Phone p : mPhones) {
+                mSlotsSupportingSimultaneousCellularCalls.add(p.getPhoneId());
+            }
+            updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+            notifySimultaneousCellularCallingSlotsChanged();
+        }
+        // Register for subId updates to notify listeners when simultaneous calling is configured
+        if (mFeatureFlags.simultaneousCallingIndications()
+                && (bkwdsCompatDsda || halSupportSimulCalling)) {
+            mContext.getSystemService(TelephonyRegistryManager.class)
+                    .addOnSubscriptionsChangedListener(
+                            new SubscriptionManager.OnSubscriptionsChangedListener() {
+                                @Override
+                                public void onSubscriptionsChanged() {
+                                    updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+                                }
+                            }, mHandler::post);
+        }
+    }
+
     /**
      * Static method to get instance.
      */
@@ -215,6 +305,7 @@
                     if (ar != null && ar.exception == null) {
                         mStaticCapability = (PhoneCapability) ar.result;
                         notifyCapabilityChanged();
+                        maybeEnableCellularDSDASupport();
                     } else {
                         log(msg.what + " failure. Not getting phone capability." + ar.exception);
                     }
@@ -228,6 +319,45 @@
                         mVirtualDsdaEnabled = isVirtualDsdaEnabled;
                     }
                     break;
+                case EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED:
+                case EVENT_GET_SIMULTANEOUS_CALLING_SUPPORT_DONE:
+                    log("Received EVENT_SLOTS_SUPPORTING_SIMULTANEOUS_CALL_CHANGED/DONE");
+                    if (getPhoneCount() < 2) {
+                        if (!mSlotsSupportingSimultaneousCellularCalls.isEmpty()) {
+                            mSlotsSupportingSimultaneousCellularCalls.clear();
+                        }
+                        break;
+                    }
+                    ar = (AsyncResult) msg.obj;
+                    if (ar != null && ar.exception == null) {
+                        int[] returnedIntArray = (int[]) ar.result;
+                        if (!mSlotsSupportingSimultaneousCellularCalls.isEmpty()) {
+                            mSlotsSupportingSimultaneousCellularCalls.clear();
+                        }
+                        int maxValidPhoneSlot = getPhoneCount() - 1;
+                        for (int i : returnedIntArray) {
+                            if (i < 0 || i > maxValidPhoneSlot) {
+                                loge("Invalid slot supporting DSDA =" + i + ". Disabling DSDA.");
+                                mSlotsSupportingSimultaneousCellularCalls.clear();
+                                break;
+                            }
+                            mSlotsSupportingSimultaneousCellularCalls.add(i);
+                        }
+                        // Ensure the slots supporting cellular DSDA does not exceed the phone count
+                        if (mSlotsSupportingSimultaneousCellularCalls.size() > getPhoneCount()) {
+                            loge("Invalid size of DSDA slots. Disabling cellular DSDA.");
+                            mSlotsSupportingSimultaneousCellularCalls.clear();
+                        }
+                    } else {
+                        log(msg.what + " failure. Not getting logical slots that support "
+                                + "simultaneous calling." + ar.exception);
+                        mSlotsSupportingSimultaneousCellularCalls.clear();
+                    }
+                    if (mFeatureFlags.simultaneousCallingIndications()) {
+                        updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+                        notifySimultaneousCellularCallingSlotsChanged();
+                    }
+                    break;
                 default:
                     log("Unknown event: " + msg.what);
             }
@@ -333,6 +463,27 @@
         return mTelephonyManager.getActiveModemCount();
     }
 
+    @VisibleForTesting
+    public Set<Integer> getSlotsSupportingSimultaneousCellularCalls() {
+        return mSlotsSupportingSimultaneousCellularCalls;
+    }
+
+    /**
+     * Get the current the list of logical slots supporting simultaneous cellular calling from the
+     * modem based on current network conditions.
+     */
+    @VisibleForTesting
+    public void updateSimultaneousCallingSupport() {
+        log("updateSimultaneousCallingSupport: sending the request for "
+                + "getting the list of logical slots supporting simultaneous cellular calling");
+        Message callback = Message.obtain(
+                mHandler, EVENT_GET_SIMULTANEOUS_CALLING_SUPPORT_DONE);
+        mRadioConfig.updateSimultaneousCallingSupport(callback);
+        log("updateSimultaneousCallingSupport: "
+                + "mSlotsSupportingSimultaneousCellularCalls = " +
+                mSlotsSupportingSimultaneousCellularCalls);
+    }
+
     /**
      * get static overall phone capabilities for all phones.
      */
@@ -343,7 +494,7 @@
                     mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
             mRadioConfig.getPhoneCapability(callback);
         }
-        mStaticCapability = maybeUpdateMaxActiveVoiceSubscriptions(mStaticCapability);
+        mStaticCapability = maybeOverrideMaxActiveVoiceSubscriptions(mStaticCapability);
         log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability);
         return mStaticCapability;
     }
@@ -359,10 +510,31 @@
         return mStaticCapability.getMaxActiveDataSubscriptions();
     }
 
-    private void notifyCapabilityChanged() {
-        PhoneNotifier notifier = new DefaultPhoneNotifier(mContext);
+    /**
+     * Register to listen to changes in the Phone slots that support simultaneous calling.
+     * @param consumer A consumer that will be used to consume the new slots supporting simultaneous
+     *                 cellular calling when it changes.
+     */
+    public void registerForSimultaneousCellularCallingSlotsChanged(
+            Consumer<Set<Integer>> consumer) {
+        mSimultaneousCellularCallingListeners.add(consumer);
+    }
 
-        notifier.notifyPhoneCapabilityChanged(mStaticCapability);
+    private void notifySimultaneousCellularCallingSlotsChanged() {
+        log("notifying listeners of changes to simultaneous cellular calling - new state:"
+                + mSlotsSupportingSimultaneousCellularCalls);
+        for (Consumer<Set<Integer>> consumer : mSimultaneousCellularCallingListeners) {
+            try {
+                consumer.accept(new HashSet<>(mSlotsSupportingSimultaneousCellularCalls));
+            } catch (Exception e) {
+                log("Unexpected Exception encountered when notifying listener: " + e);
+            }
+        }
+    }
+
+    private void notifyCapabilityChanged() {
+        mNotifier.notifyPhoneCapabilityChanged(maybeOverrideMaxActiveVoiceSubscriptions(
+                mStaticCapability));
     }
 
     /**
@@ -438,6 +610,20 @@
                 phone.mCi.onSlotActiveStatusChange(SubscriptionManager.isValidPhoneId(phoneId));
             }
 
+            if (numOfActiveModems > 1) {
+                // Check if cellular DSDA is supported. If it is, then send a request to the
+                // modem to refresh the list of SIM slots that currently support DSDA based on
+                // current network conditions
+                maybeEnableCellularDSDASupport();
+            } else {
+                // The number of active modems is 0 or 1, disable cellular DSDA:
+                mSlotsSupportingSimultaneousCellularCalls.clear();
+                if (mFeatureFlags.simultaneousCallingIndications()) {
+                    updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
+                    notifySimultaneousCellularCallingSlotsChanged();
+                }
+            }
+
             // When the user enables DSDS mode, the default VOICE and SMS subId should be switched
             // to "No Preference".  Doing so will sync the network/sim settings and telephony.
             // (see b/198123192)
@@ -613,6 +799,13 @@
                 Context context, int numOfActiveModems) {
             PhoneFactory.onMultiSimConfigChanged(context, numOfActiveModems);
         }
+
+        /**
+         * Wrapper function to query the sysprop for multi_sim_config
+         */
+        public Optional<String> getMultiSimProperty() {
+            return TelephonyProperties.multi_sim_config();
+        }
     }
 
     private static void log(String s) {
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 57a375b..b1ff500 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -23,6 +23,7 @@
 
 import static java.util.Arrays.copyOf;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -47,6 +48,8 @@
 import com.android.internal.telephony.data.TelephonyNetworkFactory;
 import com.android.internal.telephony.euicc.EuiccCardController;
 import com.android.internal.telephony.euicc.EuiccController;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneFactory;
 import com.android.internal.telephony.metrics.MetricsCollector;
@@ -59,6 +62,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -102,11 +106,17 @@
     static private final HashMap<String, LocalLog>sLocalLogs = new HashMap<String, LocalLog>();
     private static MetricsCollector sMetricsCollector;
     private static RadioInterfaceCapabilityController sRadioHalCapabilities;
+    private static @NonNull FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
 
     //***** Class Methods
 
-    public static void makeDefaultPhones(Context context) {
-        makeDefaultPhone(context);
+    /**
+     * @param context The context.
+     * @param featureFlags The feature flag.
+     */
+    public static void makeDefaultPhones(Context context, @NonNull FeatureFlags featureFlags) {
+        sFeatureFlags = featureFlags;
+        makeDefaultPhone(context, featureFlags);
     }
 
     /**
@@ -114,7 +124,7 @@
      * instances
      */
     @UnsupportedAppUsage
-    public static void makeDefaultPhone(Context context) {
+    public static void makeDefaultPhone(Context context, @NonNull FeatureFlags featureFlags) {
         synchronized (sLockProxyPhones) {
             if (!sMadeDefaults) {
                 sContext = context;
@@ -151,9 +161,9 @@
                 }
 
                 // register statsd pullers.
-                sMetricsCollector = new MetricsCollector(context);
+                sMetricsCollector = new MetricsCollector(context, sFeatureFlags);
 
-                sPhoneNotifier = new DefaultPhoneNotifier(context);
+                sPhoneNotifier = new DefaultPhoneNotifier(context, featureFlags);
 
                 int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);
                 Rlog.i(LOG_TAG, "Cdma Subscription set to " + cdmaSubscription);
@@ -198,15 +208,15 @@
 
                 Rlog.i(LOG_TAG, "Creating SubscriptionManagerService");
                 sSubscriptionManagerService = new SubscriptionManagerService(context,
-                        Looper.myLooper());
+                        Looper.myLooper(), featureFlags);
 
                 TelephonyComponentFactory.getInstance().inject(MultiSimSettingController.class.
                         getName()).initMultiSimSettingController(context);
 
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_EUICC)) {
-                    sEuiccController = EuiccController.init(context);
-                    sEuiccCardController = EuiccCardController.init(context);
+                    sEuiccController = EuiccController.init(context, sFeatureFlags);
+                    sEuiccCardController = EuiccCardController.init(context, sFeatureFlags);
                 }
 
                 for (int i = 0; i < numPhones; i++) {
@@ -246,7 +256,7 @@
                     Rlog.i(LOG_TAG, "IMS is not supported on this device, skipping ImsResolver.");
                 }
 
-                sPhoneConfigurationManager = PhoneConfigurationManager.init(sContext);
+                sPhoneConfigurationManager = PhoneConfigurationManager.init(sContext, featureFlags);
 
                 sCellularNetworkValidator = CellularNetworkValidator.make(sContext);
 
@@ -255,9 +265,10 @@
 
                 sPhoneSwitcher = TelephonyComponentFactory.getInstance().inject(
                         PhoneSwitcher.class.getName()).
-                        makePhoneSwitcher(maxActivePhones, sContext, Looper.myLooper());
+                        makePhoneSwitcher(maxActivePhones, sContext, Looper.myLooper(),
+                                featureFlags);
 
-                sProxyController = ProxyController.getInstance(context);
+                sProxyController = ProxyController.getInstance(context, featureFlags);
 
                 sIntentBroadcaster = IntentBroadcaster.getInstance(context);
 
@@ -265,7 +276,7 @@
 
                 for (int i = 0; i < numPhones; i++) {
                     sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                            Looper.myLooper(), sPhones[i]);
+                            Looper.myLooper(), sPhones[i], featureFlags);
                 }
             }
         }
@@ -274,8 +285,9 @@
     /**
      * Upon single SIM to dual SIM switch or vice versa, we dynamically allocate or de-allocate
      * Phone and CommandInterface objects.
-     * @param context
-     * @param activeModemCount
+     *
+     * @param context The context
+     * @param activeModemCount The number of active modems
      */
     public static void onMultiSimConfigChanged(Context context, int activeModemCount) {
         synchronized (sLockProxyPhones) {
@@ -302,7 +314,7 @@
                     sPhones[i].createImsPhone();
                 }
                 sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                        Looper.myLooper(), sPhones[i]);
+                        Looper.myLooper(), sPhones[i], sFeatureFlags);
             }
         }
     }
@@ -318,7 +330,7 @@
 
         return injectedComponentFactory.makePhone(context,
                 sCommandsInterfaces[phoneId], sPhoneNotifier, phoneId, phoneType,
-                TelephonyComponentFactory.getInstance());
+                TelephonyComponentFactory.getInstance(), sFeatureFlags);
     }
 
     @UnsupportedAppUsage
@@ -446,7 +458,7 @@
      * @return the {@code ImsPhone} object or null if the exception occured
      */
     public static Phone makeImsPhone(PhoneNotifier phoneNotifier, Phone defaultPhone) {
-        return ImsPhoneFactory.makePhone(sContext, phoneNotifier, defaultPhone);
+        return ImsPhoneFactory.makePhone(sContext, phoneNotifier, defaultPhone, sFeatureFlags);
     }
 
     /**
@@ -512,6 +524,27 @@
         return sMetricsCollector;
     }
 
+    /**
+     * Print all feature flag configurations that Telephony is using for debugging purposes.
+     */
+    private static void reflectAndPrintFlagConfigs(IndentingPrintWriter pw) {
+
+        try {
+            // Look away, a forbidden technique (reflection) is being used to allow us to get
+            // all flag configs without having to add them manually to this method.
+            Method[] methods = FeatureFlags.class.getMethods();
+            if (methods.length == 0) {
+                pw.println("NONE");
+                return;
+            }
+            for (Method m : methods) {
+                pw.println(m.getName() + "-> " + m.invoke(sFeatureFlags));
+            }
+        } catch (Exception e) {
+            pw.println("[ERROR]");
+        }
+    }
+
     public static void dump(FileDescriptor fd, PrintWriter printwriter, String[] args) {
         IndentingPrintWriter pw = new IndentingPrintWriter(printwriter, "  ");
         pw.println("PhoneFactory:");
@@ -604,8 +637,15 @@
         } catch (Exception e) {
             e.printStackTrace();
         }
-
         pw.flush();
         pw.decreaseIndent();
+
+        pw.println("++++++++++++++++++++++++++++++++");
+        pw.println("Flag Configurations:");
+        pw.increaseIndent();
+        reflectAndPrintFlagConfigs(pw);
+        pw.flush();
+        pw.decreaseIndent();
+        pw.println("++++++++++++++++++++++++++++++++");
     }
 }
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 20d6702..cb6b199 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -39,6 +39,7 @@
 import android.telephony.ims.MediaQualityStatus;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * {@hide}
@@ -149,4 +150,7 @@
     /** Notify callback mode stopped. */
     void notifyCallbackModeStopped(Phone sender, @EmergencyCallbackModeType int type,
             @EmergencyCallbackModeStopReason int reason);
+
+    /** Notify that simultaneous cellular calling subscriptions have changed */
+    void notifySimultaneousCellularCallingSubscriptionsChanged(Set<Integer> subIds);
 }
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index d30a73c..a65bfeb 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -20,10 +20,12 @@
 
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -39,6 +41,8 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IsimRecords;
@@ -58,8 +62,14 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private final Context mContext;
     private AppOpsManager mAppOps;
+    private FeatureFlags mFeatureFlags;
+    private PackageManager mPackageManager;
 
     public PhoneSubInfoController(Context context) {
+        this(context, new FeatureFlagsImpl());
+    }
+
+    public PhoneSubInfoController(Context context, FeatureFlags featureFlags) {
         ServiceRegisterer phoneSubServiceRegisterer = TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
                 .getPhoneSubServiceRegisterer();
@@ -68,6 +78,8 @@
         }
         mAppOps = context.getSystemService(AppOpsManager.class);
         mContext = context;
+        mPackageManager = context.getPackageManager();
+        mFeatureFlags = featureFlags;
     }
 
     @Deprecated
@@ -89,7 +101,13 @@
 
     public String getNaiForSubscriber(int subId, String callingPackage, String callingFeatureId) {
         return callPhoneMethodForSubIdWithReadSubscriberIdentifiersCheck(subId, callingPackage,
-                callingFeatureId, "getNai", (phone)-> phone.getNai());
+                callingFeatureId, "getNai", (phone)-> {
+                    enforceTelephonyFeatureWithException(callingPackage,
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+                            "getNaiForSubscriber");
+
+                    return phone.getNai();
+                });
     }
 
     public String getImeiForSubscriber(int subId, String callingPackage,
@@ -102,7 +120,13 @@
                                                               String callingPackage) {
         return callPhoneMethodForSubIdWithPrivilegedCheck(subId,
                 "getCarrierInfoForImsiEncryption",
-                (phone)-> phone.getCarrierInfoForImsiEncryption(keyType, true));
+                (phone)-> {
+                    enforceTelephonyFeatureWithException(callingPackage,
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+                            "getCarrierInfoForImsiEncryption");
+
+                    return phone.getCarrierInfoForImsiEncryption(keyType, true);
+                });
     }
 
     public void setCarrierInfoForImsiEncryption(int subId, String callingPackage,
@@ -126,6 +150,10 @@
         callPhoneMethodForSubIdWithModifyCheck(subId, callingPackage,
                 "resetCarrierKeysForImsiEncryption",
                 (phone)-> {
+                    enforceTelephonyFeatureWithException(callingPackage,
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+                            "resetCarrierKeysForImsiEncryption");
+
                     phone.resetCarrierKeysForImsiEncryption();
                     return null;
                 });
@@ -166,12 +194,22 @@
         }
         if (isActive) {
             return callPhoneMethodForSubIdWithReadSubscriberIdentifiersCheck(subId, callingPackage,
-                    callingFeatureId, message, (phone) -> phone.getSubscriberId());
+                    callingFeatureId, message, (phone) -> {
+                        enforceTelephonyFeatureWithException(callingPackage,
+                                PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+                                "getSubscriberIdForSubscriber");
+
+                        return phone.getSubscriberId();
+                    });
         } else {
             if (!TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(
                     mContext, subId, callingPackage, callingFeatureId, message)) {
                 return null;
             }
+
+            enforceTelephonyFeatureWithException(callingPackage,
+                    PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getSubscriberIdForSubscriber");
+
             identity = Binder.clearCallingIdentity();
             try {
                 SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
@@ -202,7 +240,13 @@
     public String getIccSerialNumberForSubscriber(int subId, String callingPackage,
             String callingFeatureId) {
         return callPhoneMethodForSubIdWithReadSubscriberIdentifiersCheck(subId, callingPackage,
-                callingFeatureId, "getIccSerialNumber", (phone) -> phone.getIccSerialNumber());
+                callingFeatureId, "getIccSerialNumber", (phone) -> {
+                    enforceTelephonyFeatureWithException(callingPackage,
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+                            "getIccSerialNumberForSubscriber");
+
+                    return phone.getIccSerialNumber();
+                });
     }
 
     public String getLine1Number(String callingPackage, String callingFeatureId) {
@@ -216,7 +260,13 @@
             String callingFeatureId) {
         return callPhoneMethodForSubIdWithReadPhoneNumberCheck(
                 subId, callingPackage, callingFeatureId, "getLine1Number",
-                (phone)-> phone.getLine1Number());
+                (phone)-> {
+                    enforceTelephonyFeatureWithException(callingPackage,
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+                            "getLine1NumberForSubscriber");
+
+                    return phone.getLine1Number();
+                });
     }
 
     public String getLine1AlphaTag(String callingPackage, String callingFeatureId) {
@@ -251,6 +301,10 @@
             String callingFeatureId) {
         return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, callingFeatureId,
                 "getVoiceMailNumber", (phone)-> {
+                    enforceTelephonyFeatureWithException(callingPackage,
+                            PackageManager.FEATURE_TELEPHONY_CALLING,
+                            "getVoiceMailNumberForSubscriber");
+
                     String number = PhoneNumberUtils.extractNetworkPortion(
                             phone.getVoiceMailNumber());
                     if (VDBG) log("VM: getVoiceMailNUmber: " + number);
@@ -266,7 +320,13 @@
     public String getVoiceMailAlphaTagForSubscriber(int subId, String callingPackage,
             String callingFeatureId) {
         return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, callingFeatureId,
-                "getVoiceMailAlphaTag", (phone)-> phone.getVoiceMailAlphaTag());
+                "getVoiceMailAlphaTag", (phone)-> {
+                    enforceTelephonyFeatureWithException(callingPackage,
+                            PackageManager.FEATURE_TELEPHONY_CALLING,
+                            "getVoiceMailAlphaTagForSubscriber");
+
+                    return phone.getVoiceMailAlphaTag();
+                });
     }
 
     /**
@@ -381,6 +441,9 @@
     public String getIsimDomain(int subId) {
         return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getIsimDomain",
                 (phone) -> {
+                    enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getIsimDomain");
+
                     IsimRecords isim = phone.getIsimRecords();
                     if (isim != null) {
                         return isim.getIsimDomain();
@@ -422,6 +485,10 @@
         if (TelephonyPermissions.
                 checkCallingOrSelfReadPrivilegedPhoneStatePermissionOrReadPhoneNumber(
                 mContext, subId, callingPackage, callingFeatureId, "getImsPublicUserIdentities")) {
+
+            enforceTelephonyFeatureWithException(callingPackage,
+                    PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getImsPublicUserIdentities");
+
             Phone phone = getPhone(subId);
             assert phone != null;
             IsimRecords isimRecords = phone.getIsimRecords();
@@ -447,6 +514,9 @@
     public String getIsimIst(int subId) throws RemoteException {
         return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getIsimIst",
                 (phone) -> {
+                    enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getIsimIst");
+
                     IsimRecords isim = phone.getIsimRecords();
                     if (isim != null) {
                         return isim.getIsimIst();
@@ -478,6 +548,9 @@
     public String getSimServiceTable(int subId, int appType) throws RemoteException {
         return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getSimServiceTable",
                 (phone) -> {
+                    enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getSimServiceTable");
+
                     UiccPort uiccPort = phone.getUiccPort();
                     if (uiccPort == null || uiccPort.getUiccProfile() == null) {
                         loge("getSimServiceTable(): uiccPort or uiccProfile is null");
@@ -498,6 +571,9 @@
     public String getIccSimChallengeResponse(int subId, int appType, int authType, String data,
             String callingPackage, String callingFeatureId) throws RemoteException {
         CallPhoneMethodHelper<String> toExecute = (phone)-> {
+            enforceTelephonyFeatureWithException(callingPackage,
+                    PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getIccSimChallengeResponse");
+
             UiccPort uiccPort = phone.getUiccPort();
             if (uiccPort == null) {
                 loge("getIccSimChallengeResponse() uiccPort is null");
@@ -531,7 +607,13 @@
     public String getGroupIdLevel1ForSubscriber(int subId, String callingPackage,
             String callingFeatureId) {
         return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, callingFeatureId,
-                "getGroupIdLevel1", (phone)-> phone.getGroupIdLevel1());
+                "getGroupIdLevel1", (phone)-> {
+                    enforceTelephonyFeatureWithException(callingPackage,
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+                            "getGroupIdLevel1ForSubscriber");
+
+                    return phone.getGroupIdLevel1();
+                });
     }
 
     /** Below are utility methods that abstracts the flow that many public methods use:
@@ -667,6 +749,9 @@
     public Uri getSmscIdentity(int subId, int appType) throws RemoteException {
         Uri smscIdentityUri = callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getSmscIdentity",
                 (phone) -> {
+                    enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getSmscIdentity");
+
                     try {
                         String smscIdentity = null;
                         UiccPort uiccPort = phone.getUiccPort();
@@ -690,6 +775,40 @@
         return smscIdentityUri;
     }
 
+    /**
+     * Get the current calling package name.
+     * @return the current calling package name
+     */
+    @Nullable
+    private String getCurrentPackageName() {
+        if (mPackageManager == null) return null;
+        String[] callingUids = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+        return (callingUids == null) ? null : callingUids[0];
+    }
+
+    /**
+     * Make sure the device has required telephony feature
+     *
+     * @throws UnsupportedOperationException if the device does not have required telephony feature
+     */
+    private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+            @NonNull String telephonyFeature, @NonNull String methodName) {
+        if (callingPackage == null || mPackageManager == null) {
+            return;
+        }
+
+        if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+                || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+                Binder.getCallingUserHandle())) {
+            return;
+        }
+
+        if (!mPackageManager.hasSystemFeature(telephonyFeature)) {
+            throw new UnsupportedOperationException(
+                    methodName + " is unsupported without " + telephonyFeature);
+        }
+    }
+
     private void log(String s) {
         Rlog.d(TAG, s);
     }
diff --git a/src/java/com/android/internal/telephony/ProxyController.java b/src/java/com/android/internal/telephony/ProxyController.java
index ed9982e..01aed07 100644
--- a/src/java/com/android/internal/telephony/ProxyController.java
+++ b/src/java/com/android/internal/telephony/ProxyController.java
@@ -18,6 +18,7 @@
 
 import static java.util.Arrays.copyOf;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -33,6 +34,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
@@ -111,11 +113,12 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int[] mOldRadioAccessFamily;
 
+    @NonNull
+    private final FeatureFlags mFlags;
 
-    //***** Class Methods
-    public static ProxyController getInstance(Context context) {
+    public static ProxyController getInstance(Context context, FeatureFlags flags) {
         if (sProxyController == null) {
-            sProxyController = new ProxyController(context);
+            sProxyController = new ProxyController(context, flags);
         }
         return sProxyController;
     }
@@ -125,16 +128,23 @@
         return sProxyController;
     }
 
-    private ProxyController(Context context) {
+    /**
+     * Constructor
+     *
+     * @param context The context
+     * @param featureFlags Feature flags
+     */
+    public ProxyController(@NonNull Context context, @NonNull FeatureFlags featureFlags) {
         logd("Constructor - Enter");
 
         mContext = context;
+        mFlags = featureFlags;
         mPhones = PhoneFactory.getPhones();
         mPhoneSwitcher = PhoneSwitcher.getInstance();
 
         mUiccPhoneBookController = new UiccPhoneBookController();
         mPhoneSubInfoController = new PhoneSubInfoController(mContext);
-        mSmsController = new SmsController(mContext);
+        mSmsController = new SmsController(mContext, featureFlags);
         mSetRadioAccessFamilyStatus = new int[mPhones.length];
         mNewRadioAccessFamily = new int[mPhones.length];
         mOldRadioAccessFamily = new int[mPhones.length];
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 5ecdfcb..5177adb 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -36,6 +36,7 @@
 import android.hardware.radio.V1_0.RadioIndicationType;
 import android.hardware.radio.V1_0.RadioResponseInfo;
 import android.hardware.radio.V1_0.RadioResponseType;
+import android.hardware.radio.modem.ImeiInfo;
 import android.net.KeepalivePacketData;
 import android.net.LinkProperties;
 import android.os.AsyncResult;
@@ -58,13 +59,6 @@
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.BarringInfo;
 import android.telephony.CarrierRestrictionRules;
-import android.telephony.CellInfo;
-import android.telephony.CellSignalStrengthCdma;
-import android.telephony.CellSignalStrengthGsm;
-import android.telephony.CellSignalStrengthLte;
-import android.telephony.CellSignalStrengthNr;
-import android.telephony.CellSignalStrengthTdscdma;
-import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.ClientRequestStats;
 import android.telephony.DomainSelectionService;
 import android.telephony.ImsiEncryptionInfo;
@@ -73,8 +67,6 @@
 import android.telephony.NetworkScanRequest;
 import android.telephony.RadioAccessFamily;
 import android.telephony.RadioAccessSpecifier;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
 import android.telephony.SignalThresholdInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyHistogram;
@@ -104,6 +96,7 @@
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
 import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.internal.util.FunctionalUtils;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -123,7 +116,6 @@
 
 /**
  * RIL implementation of the CommandsInterface.
- *
  * {@hide}
  */
 public class RIL extends BaseCommands implements CommandsInterface {
@@ -136,8 +128,7 @@
     static final int RIL_HISTOGRAM_BUCKET_COUNT = 5;
 
     /**
-     * Wake lock timeout should be longer than the longest timeout in
-     * the vendor ril.
+     * Wake lock timeout should be longer than the longest timeout in the vendor ril.
      */
     private static final int DEFAULT_WAKE_LOCK_TIMEOUT_MS = 60000;
 
@@ -159,18 +150,6 @@
     public static final HalVersion RADIO_HAL_VERSION_UNKNOWN = HalVersion.UNKNOWN;
 
     /** @hide */
-    public static final HalVersion RADIO_HAL_VERSION_1_0 = new HalVersion(1, 0);
-
-    /** @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 */
@@ -185,8 +164,11 @@
     /** @hide */
     public static final HalVersion RADIO_HAL_VERSION_2_1 = new HalVersion(2, 1);
 
+    /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_2_2 = new HalVersion(2, 2);
+
     // Hal version
-    private Map<Integer, HalVersion> mHalVersion = new HashMap<>();
+    private final Map<Integer, HalVersion> mHalVersion = new HashMap<>();
 
     //***** Instance Variables
 
@@ -197,8 +179,7 @@
     public final WakeLock mAckWakeLock;        // Wake lock associated with ack sent
     final int mWakeLockTimeout;         // Timeout associated with request/response
     final int mAckWakeLockTimeout;      // Timeout associated with ack sent
-    // The number of wakelock requests currently active.  Don't release the lock
-    // until dec'd to 0
+    // The number of wakelock requests currently active. Don't release the lock until dec'd to 0.
     int mWakeLockCount;
 
     // Variables used to identify releasing of WL on wakelock timeouts
@@ -331,11 +312,10 @@
                             }
                             if (RILJ_LOGD) {
                                 int count = mRequestList.size();
-                                Rlog.d(RILJ_LOG_TAG, "WAKE_LOCK_TIMEOUT " +
-                                        " mRequestList=" + count);
+                                riljLog("WAKE_LOCK_TIMEOUT mRequestList=" + count);
                                 for (int i = 0; i < count; i++) {
                                     rr = mRequestList.valueAt(i);
-                                    Rlog.d(RILJ_LOG_TAG, i + ": [" + rr.mSerial + "] "
+                                    riljLog(i + ": [" + rr.mSerial + "] "
                                             + RILUtils.requestToString(rr.mRequest));
                                 }
                             }
@@ -346,7 +326,7 @@
                 case EVENT_ACK_WAKE_LOCK_TIMEOUT:
                     if (msg.arg1 == mAckWlSequenceNum && clearWakeLock(FOR_ACK_WAKELOCK)) {
                         if (RILJ_LOGV) {
-                            Rlog.d(RILJ_LOG_TAG, "ACK_WAKE_LOCK_TIMEOUT");
+                            riljLog("ACK_WAKE_LOCK_TIMEOUT");
                         }
                     }
                     break;
@@ -493,9 +473,9 @@
         clearRequestList(RADIO_NOT_AVAILABLE, false);
 
         if (service == HAL_SERVICE_RADIO) {
-            getRadioProxy(null);
+            getRadioProxy();
         } else {
-            getRadioServiceProxy(service, null);
+            getRadioServiceProxy(service);
         }
     }
 
@@ -605,10 +585,10 @@
     @VisibleForTesting
     public void setCompatVersion(int rilRequest, @NonNull HalVersion halVersion) {
         HalVersion oldVersion = getCompatVersion(rilRequest);
-        // Do not allow to set same or greater verions
+        // Do not allow to set same or greater versions
         if (oldVersion != null && halVersion.greaterOrEqual(oldVersion)) {
-            riljLoge("setCompatVersion with equal or greater one, ignored, halVerion=" + halVersion
-                    + ", oldVerion=" + oldVersion);
+            riljLoge("setCompatVersion with equal or greater one, ignored, halVersion=" + halVersion
+                    + ", oldVersion=" + oldVersion);
             return;
         }
         mCompatOverrides.put(rilRequest, halVersion);
@@ -622,7 +602,7 @@
 
     /** Returns a {@link IRadio} instance or null if the service is not available. */
     @VisibleForTesting
-    public synchronized IRadio getRadioProxy(Message result) {
+    public synchronized IRadio getRadioProxy() {
         if (mHalVersion.containsKey(HAL_SERVICE_RADIO)
                 && mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
             return null;
@@ -630,11 +610,6 @@
         if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return null;
         if (!mIsCellularSupported) {
             if (RILJ_LOGV) riljLog("getRadioProxy: Not calling getService(): wifi-only");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
             return null;
         }
 
@@ -673,39 +648,7 @@
                 }
 
                 if (mRadioProxy == null) {
-                    try {
-                        mRadioProxy = android.hardware.radio.V1_3.IRadio.getService(
-                                HIDL_SERVICE_NAME[mPhoneId], true);
-                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_3);
-                    } catch (NoSuchElementException e) {
-                    }
-                }
-
-                if (mRadioProxy == null) {
-                    try {
-                        mRadioProxy = android.hardware.radio.V1_2.IRadio.getService(
-                                HIDL_SERVICE_NAME[mPhoneId], true);
-                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_2);
-                    } catch (NoSuchElementException e) {
-                    }
-                }
-
-                if (mRadioProxy == null) {
-                    try {
-                        mRadioProxy = android.hardware.radio.V1_1.IRadio.getService(
-                                HIDL_SERVICE_NAME[mPhoneId], true);
-                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_1);
-                    } catch (NoSuchElementException e) {
-                    }
-                }
-
-                if (mRadioProxy == null) {
-                    try {
-                        mRadioProxy = android.hardware.radio.V1_0.IRadio.getService(
-                                HIDL_SERVICE_NAME[mPhoneId], true);
-                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_0);
-                    } catch (NoSuchElementException e) {
-                    }
+                    riljLoge("IRadio <1.4 is no longer supported.");
                 }
 
                 if (mRadioProxy != null) {
@@ -729,11 +672,6 @@
         if (mRadioProxy == null) {
             // getService() is a blocking call, so this should never happen
             riljLoge("getRadioProxy: mRadioProxy == null");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
         }
 
         return mRadioProxy;
@@ -745,28 +683,27 @@
      * {@link RadioImsProxy}, or null if the service is not available.
      */
     @NonNull
-    public <T extends RadioServiceProxy> T getRadioServiceProxy(Class<T> serviceClass,
-            Message result) {
+    public <T extends RadioServiceProxy> T getRadioServiceProxy(Class<T> serviceClass) {
         if (serviceClass == RadioDataProxy.class) {
-            return (T) getRadioServiceProxy(HAL_SERVICE_DATA, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_DATA);
         }
         if (serviceClass == RadioMessagingProxy.class) {
-            return (T) getRadioServiceProxy(HAL_SERVICE_MESSAGING, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_MESSAGING);
         }
         if (serviceClass == RadioModemProxy.class) {
-            return (T) getRadioServiceProxy(HAL_SERVICE_MODEM, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_MODEM);
         }
         if (serviceClass == RadioNetworkProxy.class) {
-            return (T) getRadioServiceProxy(HAL_SERVICE_NETWORK, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_NETWORK);
         }
         if (serviceClass == RadioSimProxy.class) {
-            return (T) getRadioServiceProxy(HAL_SERVICE_SIM, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_SIM);
         }
         if (serviceClass == RadioVoiceProxy.class) {
-            return (T) getRadioServiceProxy(HAL_SERVICE_VOICE, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_VOICE);
         }
         if (serviceClass == RadioImsProxy.class) {
-            return (T) getRadioServiceProxy(HAL_SERVICE_IMS, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_IMS);
         }
         riljLoge("getRadioServiceProxy: unrecognized " + serviceClass);
         return null;
@@ -778,18 +715,13 @@
      */
     @VisibleForTesting
     @NonNull
-    public synchronized RadioServiceProxy getRadioServiceProxy(int service, Message result) {
+    public synchronized RadioServiceProxy getRadioServiceProxy(int service) {
         if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return mServiceProxies.get(service);
         if ((service >= HAL_SERVICE_IMS) && !isRadioServiceSupported(service)) {
             return mServiceProxies.get(service);
         }
         if (!mIsCellularSupported) {
             if (RILJ_LOGV) riljLog("getRadioServiceProxy: Not calling getService(): wifi-only");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
             return mServiceProxies.get(service);
         }
 
@@ -947,46 +879,7 @@
 
                 if (serviceProxy.isEmpty()
                         && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
-                    try {
-                        mHalVersion.put(service, RADIO_HAL_VERSION_1_3);
-                        serviceProxy.setHidl(mHalVersion.get(service),
-                                android.hardware.radio.V1_3.IRadio.getService(
-                                        HIDL_SERVICE_NAME[mPhoneId], true));
-                    } catch (NoSuchElementException e) {
-                    }
-                }
-
-                if (serviceProxy.isEmpty()
-                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
-                    try {
-                        mHalVersion.put(service, RADIO_HAL_VERSION_1_2);
-                        serviceProxy.setHidl(mHalVersion.get(service),
-                                android.hardware.radio.V1_2.IRadio.getService(
-                                        HIDL_SERVICE_NAME[mPhoneId], true));
-                    } catch (NoSuchElementException e) {
-                    }
-                }
-
-                if (serviceProxy.isEmpty()
-                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
-                    try {
-                        mHalVersion.put(service, RADIO_HAL_VERSION_1_1);
-                        serviceProxy.setHidl(mHalVersion.get(service),
-                                android.hardware.radio.V1_1.IRadio.getService(
-                                        HIDL_SERVICE_NAME[mPhoneId], true));
-                    } catch (NoSuchElementException e) {
-                    }
-                }
-
-                if (serviceProxy.isEmpty()
-                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
-                    try {
-                        mHalVersion.put(service, RADIO_HAL_VERSION_1_0);
-                        serviceProxy.setHidl(mHalVersion.get(service),
-                                android.hardware.radio.V1_0.IRadio.getService(
-                                        HIDL_SERVICE_NAME[mPhoneId], true));
-                    } catch (NoSuchElementException e) {
-                    }
+                    riljLoge("IRadio <1.4 is no longer supported.");
                 }
 
                 if (!serviceProxy.isEmpty()) {
@@ -1036,8 +929,7 @@
                                 break;
                         }
                     } else {
-                        if (mHalVersion.get(service)
-                                .greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+                        if (mHalVersion.get(service).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
                             throw new AssertionError("serviceProxy shouldn't be HIDL with HAL 2.0");
                         }
                         if (!mIsRadioProxyInitialized) {
@@ -1063,11 +955,6 @@
         if (serviceProxy.isEmpty()) {
             // getService() is a blocking call, so this should never happen
             riljLoge("getRadioServiceProxy: serviceProxy == null");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
         }
 
         return serviceProxy;
@@ -1080,9 +967,9 @@
             if (active) {
                 // Try to connect to RIL services and set response functions.
                 if (service == HAL_SERVICE_RADIO) {
-                    getRadioProxy(null);
+                    getRadioProxy();
                 } else {
-                    getRadioServiceProxy(service, null);
+                    getRadioServiceProxy(service);
                 }
             } else {
                 resetProxyAndRequestList(service);
@@ -1164,9 +1051,9 @@
                     }
                 } catch (SecurityException ex) {
                     /* TODO(b/211920208): instead of the following workaround (guessing if
-                    * we're in a test based on proxies being populated), mock ServiceManager
-                    * to not throw SecurityException and return correct value based on what
-                    * HAL we're testing. */
+                     * we're in a test based on proxies being populated), mock ServiceManager
+                     * to not throw SecurityException and return correct value based on what
+                     * HAL we're testing. */
                     if (proxies == null) throw ex;
                 }
                 mDeathRecipients.put(service, new BinderServiceDeathRecipient(service));
@@ -1211,11 +1098,11 @@
         // 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 (service == HAL_SERVICE_RADIO) {
-                getRadioProxy(null);
+                getRadioProxy();
             } else {
                 if (proxies == null) {
                     // Prevent telephony tests from calling the service
-                    getRadioServiceProxy(service, null);
+                    getRadioServiceProxy(service);
                 }
             }
 
@@ -1292,7 +1179,8 @@
     private void addRequest(RILRequest rr) {
         acquireWakeLock(rr, FOR_WAKELOCK);
         Trace.asyncTraceForTrackBegin(
-                Trace.TRACE_TAG_NETWORK, "RIL", RILUtils.requestToString(rr.mRequest), rr.mSerial);
+                Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial + "> "
+                + RILUtils.requestToString(rr.mRequest), rr.mSerial);
         synchronized (mRequestList) {
             rr.mStartTimeMs = SystemClock.elapsedRealtime();
             mRequestList.append(rr.mSerial, rr);
@@ -1319,45 +1207,79 @@
         resetProxyAndRequestList(service);
     }
 
+    private void radioServiceInvokeHelper(int service, RILRequest rr, String methodName,
+            FunctionalUtils.ThrowingRunnable helper) {
+        try {
+            helper.runOrThrow();
+        } catch (RuntimeException e) {
+            riljLoge(methodName + " RuntimeException: " + e);
+            int error = RadioError.SYSTEM_ERR;
+            int responseType = RadioResponseType.SOLICITED;
+            processResponseInternal(service, rr.mSerial, error, responseType);
+            processResponseDoneInternal(rr, error, responseType, null);
+        } catch (Exception e) {
+            handleRadioProxyExceptionForRR(service, methodName, e);
+        }
+    }
+
+    private boolean canMakeRequest(String request, RadioServiceProxy proxy, Message result,
+            HalVersion version) {
+        int service = HAL_SERVICE_RADIO;
+        if (proxy instanceof RadioDataProxy) {
+            service = HAL_SERVICE_DATA;
+        } else if (proxy instanceof RadioMessagingProxy) {
+            service = HAL_SERVICE_MESSAGING;
+        } else if (proxy instanceof RadioModemProxy) {
+            service = HAL_SERVICE_MODEM;
+        } else if (proxy instanceof RadioNetworkProxy) {
+            service = HAL_SERVICE_NETWORK;
+        } else if (proxy instanceof RadioSimProxy) {
+            service = HAL_SERVICE_SIM;
+        } else if (proxy instanceof RadioVoiceProxy) {
+            service = HAL_SERVICE_VOICE;
+        } 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)));
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
+                result.sendToTarget();
+            }
+            return false;
+        }
+        return true;
+    }
+
     @Override
     public void getIccCardStatus(Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_STATUS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.getIccCardStatus(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getIccCardStatus", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("getIccCardStatus", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
-    }
 
-    @Override
-    public void getIccSlotsStatus(Message result) {
-        if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "getIccSlotsStatus: REQUEST_NOT_SUPPORTED");
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_STATUS, result, mRILDefaultWorkSource);
 
-    @Override
-    public void setLogicalToPhysicalSlotMapping(int[] physicalSlots, Message result) {
         if (RILJ_LOGD) {
-            Rlog.d(RILJ_LOG_TAG, "setLogicalToPhysicalSlotMapping: REQUEST_NOT_SUPPORTED");
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
         }
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "getIccCardStatus", () -> {
+            simProxy.getIccCardStatus(rr.mSerial);
+        });
     }
 
     @Override
@@ -1367,22 +1289,22 @@
 
     @Override
     public void supplyIccPinForApp(String pin, String aid, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_PIN, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " aid = " + aid);
-            }
-
-            try {
-                simProxy.supplyIccPinForApp(rr.mSerial, RILUtils.convertNullToEmptyString(pin),
-                        RILUtils.convertNullToEmptyString(aid));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPinForApp", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("supplyIccPinForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_PIN, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " aid = " + aid);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "supplyIccPinForApp", () -> {
+            simProxy.supplyIccPinForApp(rr.mSerial, RILUtils.convertNullToEmptyString(pin),
+                    RILUtils.convertNullToEmptyString(aid));
+        });
     }
 
     @Override
@@ -1392,24 +1314,24 @@
 
     @Override
     public void supplyIccPukForApp(String puk, String newPin, String aid, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_PUK, result, mRILDefaultWorkSource);
-
-            String pukStr = RILUtils.convertNullToEmptyString(puk);
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " isPukEmpty = " + pukStr.isEmpty() + " aid = " + aid);
-            }
-
-            try {
-                simProxy.supplyIccPukForApp(rr.mSerial, pukStr,
-                        RILUtils.convertNullToEmptyString(newPin),
-                        RILUtils.convertNullToEmptyString(aid));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPukForApp", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("supplyIccPukForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_PUK, result, mRILDefaultWorkSource);
+
+        String pukStr = RILUtils.convertNullToEmptyString(puk);
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " isPukEmpty = " + pukStr.isEmpty() + " aid = " + aid);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "supplyIccPukForApp", () -> {
+            simProxy.supplyIccPukForApp(rr.mSerial, pukStr,
+                    RILUtils.convertNullToEmptyString(newPin),
+                    RILUtils.convertNullToEmptyString(aid));
+        });
     }
 
     @Override
@@ -1419,23 +1341,22 @@
 
     @Override
     public void supplyIccPin2ForApp(String pin, String aid, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_PIN2, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " aid = " + aid);
-            }
-
-            try {
-                simProxy.supplyIccPin2ForApp(rr.mSerial, RILUtils.convertNullToEmptyString(pin),
-                        RILUtils.convertNullToEmptyString(aid));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPin2ForApp", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("supplyIccPin2ForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_PIN2, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " aid = " + aid);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "supplyIccPin2ForApp", () -> {
+            simProxy.supplyIccPin2ForApp(rr.mSerial, RILUtils.convertNullToEmptyString(pin),
+                    RILUtils.convertNullToEmptyString(aid));
+        });
     }
 
     @Override
@@ -1445,24 +1366,23 @@
 
     @Override
     public void supplyIccPuk2ForApp(String puk, String newPin2, String aid, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_PUK2, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " aid = " + aid);
-            }
-
-            try {
-                simProxy.supplyIccPuk2ForApp(rr.mSerial, RILUtils.convertNullToEmptyString(puk),
-                        RILUtils.convertNullToEmptyString(newPin2),
-                        RILUtils.convertNullToEmptyString(aid));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPuk2ForApp", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("supplyIccPuk2ForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_PUK2, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " aid = " + aid);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "supplyIccPuk2ForApp", () -> {
+            simProxy.supplyIccPuk2ForApp(rr.mSerial, RILUtils.convertNullToEmptyString(puk),
+                    RILUtils.convertNullToEmptyString(newPin2),
+                    RILUtils.convertNullToEmptyString(aid));
+        });
     }
 
     @Override
@@ -1472,25 +1392,24 @@
 
     @Override
     public void changeIccPinForApp(String oldPin, String newPin, String aid, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CHANGE_SIM_PIN, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " oldPin = " + oldPin + " newPin = " + newPin + " aid = " + aid);
-            }
-
-            try {
-                simProxy.changeIccPinForApp(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(oldPin),
-                        RILUtils.convertNullToEmptyString(newPin),
-                        RILUtils.convertNullToEmptyString(aid));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "changeIccPinForApp", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("changeIccPinForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CHANGE_SIM_PIN, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " oldPin = " + oldPin + " newPin = " + newPin + " aid = " + aid);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "changeIccPinForApp", () -> {
+            simProxy.changeIccPinForApp(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(oldPin),
+                    RILUtils.convertNullToEmptyString(newPin),
+                    RILUtils.convertNullToEmptyString(aid));
+        });
     }
 
     @Override
@@ -1500,102 +1419,92 @@
 
     @Override
     public void changeIccPin2ForApp(String oldPin2, String newPin2, String aid, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CHANGE_SIM_PIN2, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " oldPin = " + oldPin2 + " newPin = " + newPin2 + " aid = " + aid);
-            }
-
-            try {
-                simProxy.changeIccPin2ForApp(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(oldPin2),
-                        RILUtils.convertNullToEmptyString(newPin2),
-                        RILUtils.convertNullToEmptyString(aid));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "changeIccPin2ForApp", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("changeIccPin2ForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CHANGE_SIM_PIN2, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " oldPin = " + oldPin2 + " newPin = " + newPin2 + " aid = " + aid);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "changeIccPin2ForApp", () -> {
+            simProxy.changeIccPin2ForApp(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(oldPin2),
+                    RILUtils.convertNullToEmptyString(newPin2),
+                    RILUtils.convertNullToEmptyString(aid));
+        });
     }
 
     @Override
     public void supplyNetworkDepersonalization(String netpin, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " netpin = " + netpin);
-            }
-
-            try {
-                networkProxy.supplyNetworkDepersonalization(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(netpin));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(
-                        HAL_SERVICE_NETWORK, "supplyNetworkDepersonalization", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("supplyNetworkDepersonalization", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " netpin = " + netpin);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "supplyNetworkDepersonalization", () -> {
+            networkProxy.supplyNetworkDepersonalization(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(netpin));
+        });
     }
 
     @Override
     public void supplySimDepersonalization(PersoSubState persoType, String controlKey,
             Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (simProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " controlKey = " + controlKey + " persoType" + persoType);
-            }
-
-            try {
-                simProxy.supplySimDepersonalization(rr.mSerial, persoType,
-                        RILUtils.convertNullToEmptyString(controlKey));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplySimDepersonalization", e);
-            }
-        } else {
-            if (PersoSubState.PERSOSUBSTATE_SIM_NETWORK == persoType) {
-                supplyNetworkDepersonalization(controlKey, result);
-                return;
-            }
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "supplySimDepersonalization: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        if (mHalVersion.get(HAL_SERVICE_SIM).less(RADIO_HAL_VERSION_1_5)
+                && PersoSubState.PERSOSUBSTATE_SIM_NETWORK == persoType) {
+            supplyNetworkDepersonalization(controlKey, result);
+            return;
         }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("supplySimDepersonalization", simProxy, result,
+                RADIO_HAL_VERSION_1_5)) {
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " controlKey = " + controlKey + " persoType" + persoType);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "supplySimDepersonalization", () -> {
+            simProxy.supplySimDepersonalization(rr.mSerial, persoType,
+                    RILUtils.convertNullToEmptyString(controlKey));
+        });
     }
 
     @Override
     public void getCurrentCalls(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_CURRENT_CALLS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.getCurrentCalls(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCurrentCalls", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("getCurrentCalls", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_CURRENT_CALLS, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "getCurrentCalls", () -> {
+            voiceProxy.getCurrentCalls(rr.mSerial);
+        });
     }
 
     @Override
@@ -1607,167 +1516,127 @@
 
     @Override
     public void enableModem(boolean enable, Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (modemProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_MODEM).greaterOrEqual(RADIO_HAL_VERSION_1_3)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_MODEM, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " enable = " + enable);
-            }
-
-            try {
-                modemProxy.enableModem(rr.mSerial, enable);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM,
-                        "enableModem", e);
-            }
-        } else {
-            if (RILJ_LOGV) riljLog("enableModem: not supported.");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("enableModem", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_MODEM, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enable = " + enable);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "enableModem", () -> {
+            modemProxy.enableModem(rr.mSerial, enable);
+        });
     }
 
     @Override
     public void setSystemSelectionChannels(@NonNull List<RadioAccessSpecifier> specifiers,
             Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_3)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " setSystemSelectionChannels_1.3= " + specifiers);
-            }
-
-            try {
-                networkProxy.setSystemSelectionChannels(rr.mSerial, specifiers);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "setSystemSelectionChannels", e);
-            }
-        } else {
-            if (RILJ_LOGV) riljLog("setSystemSelectionChannels: not supported.");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setSystemSelectionChannels", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " setSystemSelectionChannels= " + specifiers);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setSystemSelectionChannels", () -> {
+            networkProxy.setSystemSelectionChannels(rr.mSerial, specifiers);
+        });
     }
 
     @Override
     public void getSystemSelectionChannels(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " getSystemSelectionChannels");
-            }
-
-            try {
-                networkProxy.getSystemSelectionChannels(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "getSystemSelectionChannels", e);
-            }
-        } else {
-            if (RILJ_LOGV) riljLog("getSystemSelectionChannels: not supported.");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getSystemSelectionChannels", networkProxy, result,
+                RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " getSystemSelectionChannels");
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getSystemSelectionChannels", () -> {
+            networkProxy.getSystemSelectionChannels(rr.mSerial);
+        });
     }
 
     @Override
     public void getModemStatus(Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (modemProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_MODEM).greaterOrEqual(RADIO_HAL_VERSION_1_3)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_MODEM_STATUS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                modemProxy.getModemStackStatus(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getModemStatus", e);
-            }
-        } else {
-            if (RILJ_LOGV) riljLog("getModemStatus: not supported.");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("getModemStatus", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_MODEM_STATUS, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "getModemStatus", () -> {
+            modemProxy.getModemStackStatus(rr.mSerial);
+        });
     }
 
     @Override
     public void dial(String address, boolean isEmergencyCall, EmergencyNumber emergencyNumberInfo,
             boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result) {
-        if (isEmergencyCall
-                && mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_1_4)
-                && emergencyNumberInfo != null) {
+        if (isEmergencyCall && emergencyNumberInfo != null) {
             emergencyDial(address, emergencyNumberInfo, hasKnownUserIntentEmergency, clirMode,
                     uusInfo, result);
             return;
         }
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DIAL, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                // Do not log function arg for privacy
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.dial(rr.mSerial, address, clirMode, uusInfo);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "dial", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("dial", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_DIAL, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            // Do not log function arg for privacy
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "dial", () -> {
+            voiceProxy.dial(rr.mSerial, address, clirMode, uusInfo);
+        });
     }
 
     private void emergencyDial(String address, EmergencyNumber emergencyNumberInfo,
             boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (voiceProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_EMERGENCY_DIAL, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                // Do not log function arg for privacy
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.emergencyDial(rr.mSerial, RILUtils.convertNullToEmptyString(address),
-                        emergencyNumberInfo, hasKnownUserIntentEmergency, clirMode, uusInfo);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "emergencyDial", e);
-            }
-        } else {
-            riljLoge("emergencyDial is not supported with 1.4 below IRadio");
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("emergencyDial", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_EMERGENCY_DIAL, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            // Do not log function arg for privacy
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "emergencyDial", () -> {
+            voiceProxy.emergencyDial(rr.mSerial, RILUtils.convertNullToEmptyString(address),
+                    emergencyNumberInfo, hasKnownUserIntentEmergency, clirMode, uusInfo);
+        });
     }
 
     @Override
@@ -1777,304 +1646,305 @@
 
     @Override
     public void getIMSIForApp(String aid, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_IMSI, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " aid = " + aid);
-            }
-            try {
-                simProxy.getImsiForApp(rr.mSerial, RILUtils.convertNullToEmptyString(aid));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getImsiForApp", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("getIMSIForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_IMSI, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " aid = " + aid);
+        }
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "getIMSIForApp", () -> {
+            simProxy.getImsiForApp(rr.mSerial, RILUtils.convertNullToEmptyString(aid));
+        });
     }
 
     @Override
     public void hangupConnection(int gsmIndex, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_HANGUP, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " gsmIndex = " + gsmIndex);
-            }
-
-            try {
-                voiceProxy.hangup(rr.mSerial, gsmIndex);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "hangup", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("hangupConnection", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_HANGUP, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " gsmIndex = " + gsmIndex);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "hangupConnection", () -> {
+            voiceProxy.hangup(rr.mSerial, gsmIndex);
+        });
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public void hangupWaitingOrBackground(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.hangupWaitingOrBackground(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "hangupWaitingOrBackground", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("hangupWaitingOrBackground", voiceProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "hangupWaitingOrBackground", () -> {
+            voiceProxy.hangupWaitingOrBackground(rr.mSerial);
+        });
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public void hangupForegroundResumeBackground(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.hangupForegroundResumeBackground(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(
-                        HAL_SERVICE_VOICE, "hangupForegroundResumeBackground", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("hangupForegroundResumeBackground", voiceProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "hangupForegroundResumeBackground", () -> {
+            voiceProxy.hangupForegroundResumeBackground(rr.mSerial);
+        });
     }
 
     @Override
     public void switchWaitingOrHoldingAndActive(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.switchWaitingOrHoldingAndActive(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE,
-                        "switchWaitingOrHoldingAndActive", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("switchWaitingOrHoldingAndActive", voiceProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "switchWaitingOrHoldingAndActive", () -> {
+            voiceProxy.switchWaitingOrHoldingAndActive(rr.mSerial);
+        });
     }
 
     @Override
     public void conference(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CONFERENCE, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.conference(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "conference", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("conference", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CONFERENCE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "conference", () -> {
+            voiceProxy.conference(rr.mSerial);
+        });
     }
 
     @Override
     public void rejectCall(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_UDUB, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.rejectCall(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "rejectCall", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("rejectCall", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_UDUB, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "rejectCall", () -> {
+            voiceProxy.rejectCall(rr.mSerial);
+        });
     }
 
     @Override
     public void getLastCallFailCause(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_LAST_CALL_FAIL_CAUSE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.getLastCallFailCause(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getLastCallFailCause", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("getLastCallFailCause", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_LAST_CALL_FAIL_CAUSE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "getLastCallFailCause", () -> {
+            voiceProxy.getLastCallFailCause(rr.mSerial);
+        });
     }
 
     @Override
     public void getSignalStrength(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SIGNAL_STRENGTH, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getSignalStrength(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getSignalStrength", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getSignalStrength", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SIGNAL_STRENGTH, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getSignalStrength", () -> {
+            networkProxy.getSignalStrength(rr.mSerial);
+        });
     }
 
     @Override
     public void getVoiceRegistrationState(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_VOICE_REGISTRATION_STATE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_VOICE_REGISTRATION_STATE);
-            if (RILJ_LOGD) {
-                riljLog("getVoiceRegistrationState: overrideHalVersion=" + overrideHalVersion);
-            }
-
-            try {
-                networkProxy.getVoiceRegistrationState(rr.mSerial, overrideHalVersion);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getVoiceRegistrationState", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getVoiceRegistrationState", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_VOICE_REGISTRATION_STATE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_VOICE_REGISTRATION_STATE);
+        if (RILJ_LOGD) {
+            riljLog("getVoiceRegistrationState: overrideHalVersion=" + overrideHalVersion);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getVoiceRegistrationState", () -> {
+            networkProxy.getVoiceRegistrationState(rr.mSerial, overrideHalVersion);
+        });
     }
 
     @Override
     public void getDataRegistrationState(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DATA_REGISTRATION_STATE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_DATA_REGISTRATION_STATE);
-            if (RILJ_LOGD) {
-                riljLog("getDataRegistrationState: overrideHalVersion=" + overrideHalVersion);
-            }
-
-            try {
-                networkProxy.getDataRegistrationState(rr.mSerial, overrideHalVersion);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getDataRegistrationState", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getDataRegistrationState", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_DATA_REGISTRATION_STATE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_DATA_REGISTRATION_STATE);
+        if (RILJ_LOGD) {
+            riljLog("getDataRegistrationState: overrideHalVersion=" + overrideHalVersion);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getDataRegistrationState", () -> {
+            networkProxy.getDataRegistrationState(rr.mSerial, overrideHalVersion);
+        });
     }
 
     @Override
     public void getOperator(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_OPERATOR, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getOperator(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getOperator", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getOperator", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_OPERATOR, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getOperator", () -> {
+            networkProxy.getOperator(rr.mSerial);
+        });
     }
 
     @UnsupportedAppUsage
     @Override
     public void setRadioPower(boolean on, boolean forEmergencyCall,
             boolean preferredForEmergencyCall, Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_RADIO_POWER, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " on = " + on + " forEmergencyCall= " + forEmergencyCall
-                        + " preferredForEmergencyCall="  + preferredForEmergencyCall);
-            }
-
-            try {
-                modemProxy.setRadioPower(rr.mSerial, on, forEmergencyCall,
-                        preferredForEmergencyCall);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "setRadioPower", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("setRadioPower", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_RADIO_POWER, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " on = " + on + " forEmergencyCall= " + forEmergencyCall
+                    + " preferredForEmergencyCall=" + preferredForEmergencyCall);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "setRadioPower", () -> {
+            modemProxy.setRadioPower(rr.mSerial, on, forEmergencyCall,
+                    preferredForEmergencyCall);
+        });
     }
 
     @Override
     public void sendDtmf(char c, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DTMF, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                // Do not log function arg for privacy
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.sendDtmf(rr.mSerial, c + "");
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendDtmf", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("sendDtmf", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_DTMF, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            // Do not log function arg for privacy
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "sendDtmf", () -> {
+            voiceProxy.sendDtmf(rr.mSerial, c + "");
+        });
     }
 
     @Override
     public void sendSMS(String smscPdu, String pdu, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SEND_SMS, result, mRILDefaultWorkSource);
-
-            // Do not log function args for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                messagingProxy.sendSms(rr.mSerial, smscPdu, pdu);
-                mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_GSM,
-                        SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendSMS", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("sendSMS", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SEND_SMS, result, mRILDefaultWorkSource);
+
+        // Do not log function args for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "sendSMS", () -> {
+            messagingProxy.sendSms(rr.mSerial, smscPdu, pdu);
+            mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_GSM,
+                    SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result));
+        });
     }
 
     /**
@@ -2097,69 +1967,52 @@
 
     @Override
     public void sendSMSExpectMore(String smscPdu, String pdu, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SEND_SMS_EXPECT_MORE, result,
-                    mRILDefaultWorkSource);
-
-            // Do not log function arg for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                messagingProxy.sendSmsExpectMore(rr.mSerial, smscPdu, pdu);
-                mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_GSM,
-                        SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendSMSExpectMore", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("sendSMSExpectMore", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SEND_SMS_EXPECT_MORE, result,
+                mRILDefaultWorkSource);
+
+        // Do not log function arg for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "sendSMSExpectMore", () -> {
+            messagingProxy.sendSmsExpectMore(rr.mSerial, smscPdu, pdu);
+            mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_GSM,
+                    SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result));
+        });
     }
 
     @Override
-    public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
-            boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
-            NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
-            boolean matchAllRuleAllowed, Message result) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (!dataProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SETUP_DATA_CALL, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + ",reason=" + RILUtils.setupDataReasonToString(reason)
-                        + ",accessNetworkType=" + AccessNetworkType.toString(accessNetworkType)
-                        + ",dataProfile=" + dataProfile + ",isRoaming=" + isRoaming
-                        + ",allowRoaming=" + allowRoaming
-                        + ",linkProperties=" + linkProperties + ",pduSessionId=" + pduSessionId
-                        + ",sliceInfo=" + sliceInfo + ",trafficDescriptor=" + trafficDescriptor
-                        + ",matchAllRuleAllowed=" + matchAllRuleAllowed);
-            }
-
-            try {
-                dataProxy.setupDataCall(rr.mSerial, mPhoneId, accessNetworkType, dataProfile,
-                        isRoaming, allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
-                        trafficDescriptor, matchAllRuleAllowed);
-            } catch (RemoteException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setupDataCall", e);
-            } catch (RuntimeException e) {
-                riljLoge("setupDataCall RuntimeException: " + e);
-                int error = RadioError.SYSTEM_ERR;
-                int responseType = RadioResponseType.SOLICITED;
-                processResponseInternal(HAL_SERVICE_DATA, rr.mSerial, error, responseType);
-                processResponseDoneInternal(rr, error, responseType, null);
-            }
-        } else {
-            riljLoge("setupDataCall: DataProxy is empty");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
+    public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean allowRoaming,
+            int reason, LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
+            TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed, Message result) {
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("setupDataCall", dataProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SETUP_DATA_CALL, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + ",reason=" + RILUtils.setupDataReasonToString(reason)
+                    + ",accessNetworkType=" + AccessNetworkType.toString(accessNetworkType)
+                    + ",dataProfile=" + dataProfile + ",allowRoaming=" + allowRoaming
+                    + ",linkProperties=" + linkProperties + ",pduSessionId=" + pduSessionId
+                    + ",sliceInfo=" + sliceInfo + ",trafficDescriptor=" + trafficDescriptor
+                    + ",matchAllRuleAllowed=" + matchAllRuleAllowed);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "setupDataCall", () -> {
+            dataProxy.setupDataCall(rr.mSerial, accessNetworkType, dataProfile, allowRoaming,
+                    reason, linkProperties, pduSessionId, sliceInfo, trafficDescriptor,
+                    matchAllRuleAllowed);
+        });
     }
 
     @Override
@@ -2171,257 +2024,251 @@
     @Override
     public void iccIOForApp(int command, int fileId, String path, int p1, int p2, int p3,
             String data, String pin2, String aid, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SIM_IO, result, mRILDefaultWorkSource);
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("iccIOForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
+        }
 
-            if (RILJ_LOGD) {
-                if (TelephonyUtils.IS_DEBUGGABLE) {
-                    riljLog(rr.serialString() + "> iccIO: " + RILUtils.requestToString(rr.mRequest)
-                            + " command = 0x" + Integer.toHexString(command) + " fileId = 0x"
-                            + Integer.toHexString(fileId) + " path = " + path + " p1 = " + p1
-                            + " p2 = " + p2 + " p3 = " + " data = " + data + " aid = " + aid);
-                } else {
-                    riljLog(rr.serialString() + "> iccIO: "
-                            + RILUtils.requestToString(rr.mRequest));
-                }
-            }
+        RILRequest rr = obtainRequest(RIL_REQUEST_SIM_IO, result, mRILDefaultWorkSource);
 
-            try {
-                simProxy.iccIoForApp(rr.mSerial, command, fileId,
-                        RILUtils.convertNullToEmptyString(path), p1, p2, p3,
-                        RILUtils.convertNullToEmptyString(data),
-                        RILUtils.convertNullToEmptyString(pin2),
-                        RILUtils.convertNullToEmptyString(aid));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccIoForApp", e);
+        if (RILJ_LOGD) {
+            if (TelephonyUtils.IS_DEBUGGABLE) {
+                riljLog(rr.serialString() + "> iccIO: " + RILUtils.requestToString(rr.mRequest)
+                        + " command = 0x" + Integer.toHexString(command) + " fileId = 0x"
+                        + Integer.toHexString(fileId) + " path = " + path + " p1 = " + p1
+                        + " p2 = " + p2 + " p3 = " + " data = " + data + " aid = " + aid);
+            } else {
+                riljLog(rr.serialString() + "> iccIO: "
+                        + RILUtils.requestToString(rr.mRequest));
             }
         }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "iccIOForApp", () -> {
+            simProxy.iccIoForApp(rr.mSerial, command, fileId,
+                    RILUtils.convertNullToEmptyString(path), p1, p2, p3,
+                    RILUtils.convertNullToEmptyString(data),
+                    RILUtils.convertNullToEmptyString(pin2),
+                    RILUtils.convertNullToEmptyString(aid));
+        });
     }
 
     @Override
     public void sendUSSD(String ussd, Message result) {
-        RadioVoiceProxy voiceProxy =
-                getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SEND_USSD, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                String logUssd = "*******";
-                if (RILJ_LOGV) logUssd = ussd;
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " ussd = " + logUssd);
-            }
-
-            try {
-                voiceProxy.sendUssd(rr.mSerial, RILUtils.convertNullToEmptyString(ussd));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendUssd", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("sendUSSD", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SEND_USSD, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            String logUssd = "*******";
+            if (RILJ_LOGV) logUssd = ussd;
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " ussd = " + logUssd);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "sendUSSD", () -> {
+            voiceProxy.sendUssd(rr.mSerial, RILUtils.convertNullToEmptyString(ussd));
+        });
     }
 
     @Override
     public void cancelPendingUssd(Message result) {
-        RadioVoiceProxy voiceProxy =
-                getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_USSD, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.cancelPendingUssd(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "cancelPendingUssd", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("cancelPendingUssd", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_USSD, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "cancelPendingUssd", () -> {
+            voiceProxy.cancelPendingUssd(rr.mSerial);
+        });
     }
 
     @Override
     public void getCLIR(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_CLIR, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.getClir(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getClir", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("getCLIR", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_CLIR, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "getCLIR", () -> {
+            voiceProxy.getClir(rr.mSerial);
+        });
     }
 
     @Override
     public void setCLIR(int clirMode, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_CLIR, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " clirMode = " + clirMode);
-            }
-
-            try {
-                voiceProxy.setClir(rr.mSerial, clirMode);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setClir", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("setCLIR", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_CLIR, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " clirMode = " + clirMode);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "setCLIR", () -> {
+            voiceProxy.setClir(rr.mSerial, clirMode);
+        });
     }
 
     @Override
     public void queryCallForwardStatus(int cfReason, int serviceClass, String number,
             Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_CALL_FORWARD_STATUS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " cfreason = " + cfReason + " serviceClass = " + serviceClass);
-            }
-
-            try {
-                voiceProxy.getCallForwardStatus(rr.mSerial, cfReason, serviceClass, number);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCallForwardStatus", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("queryCallForwardStatus", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_CALL_FORWARD_STATUS, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " cfReason = " + cfReason + " serviceClass = " + serviceClass);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "queryCallForwardStatus", () -> {
+            voiceProxy.getCallForwardStatus(rr.mSerial, cfReason, serviceClass, number);
+        });
     }
 
     @Override
     public void setCallForward(int action, int cfReason, int serviceClass, String number,
             int timeSeconds, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_CALL_FORWARD, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " action = " + action + " cfReason = " + cfReason + " serviceClass = "
-                        + serviceClass + " timeSeconds = " + timeSeconds);
-            }
-
-            try {
-                voiceProxy.setCallForward(
-                        rr.mSerial, action, cfReason, serviceClass, number, timeSeconds);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setCallForward", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("setCallForward", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_CALL_FORWARD, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " action = " + action + " cfReason = " + cfReason + " serviceClass = "
+                    + serviceClass + " timeSeconds = " + timeSeconds);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "setCallForward", () -> {
+            voiceProxy.setCallForward(
+                    rr.mSerial, action, cfReason, serviceClass, number, timeSeconds);
+        });
     }
 
     @Override
     public void queryCallWaiting(int serviceClass, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_CALL_WAITING, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " serviceClass = " + serviceClass);
-            }
-
-            try {
-                voiceProxy.getCallWaiting(rr.mSerial, serviceClass);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCallWaiting", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("queryCallWaiting", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_CALL_WAITING, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " serviceClass = " + serviceClass);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "queryCallWaiting", () -> {
+            voiceProxy.getCallWaiting(rr.mSerial, serviceClass);
+        });
     }
 
     @Override
     public void setCallWaiting(boolean enable, int serviceClass, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_CALL_WAITING, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " enable = " + enable + " serviceClass = " + serviceClass);
-            }
-
-            try {
-                voiceProxy.setCallWaiting(rr.mSerial, enable, serviceClass);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setCallWaiting", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("setCallWaiting", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_CALL_WAITING, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enable = " + enable + " serviceClass = " + serviceClass);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "setCallWaiting", () -> {
+            voiceProxy.setCallWaiting(rr.mSerial, enable, serviceClass);
+        });
     }
 
     @Override
     public void acknowledgeLastIncomingGsmSms(boolean success, int cause, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SMS_ACKNOWLEDGE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " success = " + success + " cause = " + cause);
-            }
-
-            try {
-                messagingProxy.acknowledgeLastIncomingGsmSms(rr.mSerial, success, cause);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
-                        "acknowledgeLastIncomingGsmSms", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("acknowledgeLastIncomingGsmSms", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SMS_ACKNOWLEDGE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " success = " + success + " cause = " + cause);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "acknowledgeLastIncomingGsmSms", () -> {
+            messagingProxy.acknowledgeLastIncomingGsmSms(rr.mSerial, success, cause);
+        });
     }
 
     @Override
     public void acceptCall(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ANSWER, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.acceptCall(rr.mSerial);
-                mMetrics.writeRilAnswer(mPhoneId, rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "acceptCall", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("acceptCall", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ANSWER, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "acceptCall", () -> {
+            voiceProxy.acceptCall(rr.mSerial);
+            mMetrics.writeRilAnswer(mPhoneId, rr.mSerial);
+        });
     }
 
     @Override
     public void deactivateDataCall(int cid, int reason, Message result) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (!dataProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DEACTIVATE_DATA_CALL, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " cid = " + cid + " reason = "
-                        + RILUtils.deactivateDataReasonToString(reason));
-            }
-
-            try {
-                dataProxy.deactivateDataCall(rr.mSerial, cid, reason);
-                mMetrics.writeRilDeactivateDataCall(mPhoneId, rr.mSerial, cid, reason);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "deactivateDataCall", e);
-            }
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("deactivateDataCall", dataProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_DEACTIVATE_DATA_CALL, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " cid = " + cid + " reason = "
+                    + RILUtils.deactivateDataReasonToString(reason));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "deactivateDataCall", () -> {
+            dataProxy.deactivateDataCall(rr.mSerial, cid, reason);
+            mMetrics.writeRilDeactivateDataCall(mPhoneId, rr.mSerial, cid, reason);
+        });
     }
 
     @Override
@@ -2433,26 +2280,27 @@
     @Override
     public void queryFacilityLockForApp(String facility, String password, int serviceClass,
             String appId, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_FACILITY_LOCK, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " facility = " + facility + " serviceClass = " + serviceClass
-                        + " appId = " + appId);
-            }
-
-            try {
-                simProxy.getFacilityLockForApp(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(facility),
-                        RILUtils.convertNullToEmptyString(password),
-                        serviceClass, RILUtils.convertNullToEmptyString(appId));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getFacilityLockForApp", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("queryFacilityLockForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_FACILITY_LOCK, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " facility = " + facility + " serviceClass = " + serviceClass
+                    + " appId = " + appId);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "queryFacilityLockForApp", () -> {
+            simProxy.getFacilityLockForApp(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(facility),
+                    RILUtils.convertNullToEmptyString(password), serviceClass,
+                    RILUtils.convertNullToEmptyString(appId));
+        });
+
     }
 
     @Override
@@ -2464,131 +2312,132 @@
     @Override
     public void setFacilityLockForApp(String facility, boolean lockState, String password,
             int serviceClass, String appId, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_FACILITY_LOCK, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " facility = " + facility + " lockstate = " + lockState
-                        + " serviceClass = " + serviceClass + " appId = " + appId);
-            }
-
-            try {
-                simProxy.setFacilityLockForApp(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(facility), lockState,
-                        RILUtils.convertNullToEmptyString(password), serviceClass,
-                        RILUtils.convertNullToEmptyString(appId));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setFacilityLockForApp", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("setFacilityLockForApp", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_FACILITY_LOCK, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " facility = " + facility + " lockstate = " + lockState
+                    + " serviceClass = " + serviceClass + " appId = " + appId);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "setFacilityLockForApp", () -> {
+            simProxy.setFacilityLockForApp(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(facility), lockState,
+                    RILUtils.convertNullToEmptyString(password), serviceClass,
+                    RILUtils.convertNullToEmptyString(appId));
+        });
     }
 
     @Override
     public void changeBarringPassword(String facility, String oldPwd, String newPwd,
             Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CHANGE_BARRING_PASSWORD, result,
-                    mRILDefaultWorkSource);
-
-            // Do not log all function args for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + "facility = " + facility);
-            }
-
-            try {
-                networkProxy.setBarringPassword(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(facility),
-                        RILUtils.convertNullToEmptyString(oldPwd),
-                        RILUtils.convertNullToEmptyString(newPwd));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "changeBarringPassword", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("changeBarringPassword", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CHANGE_BARRING_PASSWORD, result,
+                mRILDefaultWorkSource);
+
+        // Do not log all function args for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + "facility = " + facility);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "changeBarringPassword", () -> {
+            networkProxy.setBarringPassword(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(facility),
+                    RILUtils.convertNullToEmptyString(oldPwd),
+                    RILUtils.convertNullToEmptyString(newPwd));
+        });
     }
 
     @Override
     public void getNetworkSelectionMode(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getNetworkSelectionMode(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getNetworkSelectionMode", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getNetworkSelectionMode", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getNetworkSelectionMode", () -> {
+            networkProxy.getNetworkSelectionMode(rr.mSerial);
+        });
     }
 
     @Override
     public void setNetworkSelectionModeAutomatic(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.setNetworkSelectionModeAutomatic(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(
-                        HAL_SERVICE_NETWORK, "setNetworkSelectionModeAutomatic", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setNetworkSelectionModeAutomatic", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setNetworkSelectionModeAutomatic",
+                () -> {
+                    networkProxy.setNetworkSelectionModeAutomatic(rr.mSerial);
+                });
     }
 
     @Override
     public void setNetworkSelectionModeManual(String operatorNumeric, int ran, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " operatorNumeric = " + operatorNumeric + ", ran = " + ran);
-            }
-
-            try {
-                networkProxy.setNetworkSelectionModeManual(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(operatorNumeric), ran);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "setNetworkSelectionModeManual", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setNetworkSelectionModeManual", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " operatorNumeric = " + operatorNumeric + ", ran = " + ran);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setNetworkSelectionModeManual", () -> {
+            networkProxy.setNetworkSelectionModeManual(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(operatorNumeric), ran);
+        });
     }
 
     @Override
     public void getAvailableNetworks(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_AVAILABLE_NETWORKS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getAvailableNetworks(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getAvailableNetworks", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getAvailableNetworks", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_AVAILABLE_NETWORKS, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getAvailableNetworks", () -> {
+            networkProxy.getAvailableNetworks(rr.mSerial);
+        });
     }
 
     /**
@@ -2599,526 +2448,486 @@
      */
     @Override
     public void startNetworkScan(NetworkScanRequest networkScanRequest, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
-            HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_START_NETWORK_SCAN);
-            if (RILJ_LOGD) {
-                riljLog("startNetworkScan: overrideHalVersion=" + overrideHalVersion);
-            }
-
-            RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result,
-                    mRILDefaultWorkSource, networkScanRequest);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.startNetworkScan(rr.mSerial, networkScanRequest, overrideHalVersion,
-                        result);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "startNetworkScan", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startNetworkScan: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("startNetworkScan", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_START_NETWORK_SCAN);
+        if (RILJ_LOGD) {
+            riljLog("startNetworkScan: overrideHalVersion=" + overrideHalVersion);
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result,
+                mRILDefaultWorkSource, networkScanRequest);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "startNetworkScan", () -> {
+            networkProxy.startNetworkScan(rr.mSerial, networkScanRequest, overrideHalVersion,
+                    result);
+        });
     }
 
     @Override
     public void stopNetworkScan(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_STOP_NETWORK_SCAN, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.stopNetworkScan(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "stopNetworkScan", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "stopNetworkScan: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("stopNetworkScan", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_STOP_NETWORK_SCAN, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "stopNetworkScan", () -> {
+            networkProxy.stopNetworkScan(rr.mSerial);
+        });
     }
 
     @Override
     public void startDtmf(char c, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DTMF_START, result, mRILDefaultWorkSource);
-
-            // Do not log function arg for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.startDtmf(rr.mSerial, c + "");
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "startDtmf", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("startDtmf", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_DTMF_START, result, mRILDefaultWorkSource);
+
+        // Do not log function arg for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "startDtmf", () -> {
+            voiceProxy.startDtmf(rr.mSerial, c + "");
+        });
     }
 
     @Override
     public void stopDtmf(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DTMF_STOP, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.stopDtmf(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "stopDtmf", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("stopDtmf", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_DTMF_STOP, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "stopDtmf", () -> {
+            voiceProxy.stopDtmf(rr.mSerial);
+        });
     }
 
     @Override
     public void separateConnection(int gsmIndex, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SEPARATE_CONNECTION, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " gsmIndex = " + gsmIndex);
-            }
-
-            try {
-                voiceProxy.separateConnection(rr.mSerial, gsmIndex);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "separateConnection", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("separateConnection", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SEPARATE_CONNECTION, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " gsmIndex = " + gsmIndex);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "separateConnection", () -> {
+            voiceProxy.separateConnection(rr.mSerial, gsmIndex);
+        });
     }
 
     @Override
     public void getBasebandVersion(Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_BASEBAND_VERSION, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                modemProxy.getBasebandVersion(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getBasebandVersion", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("getBasebandVersion", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_BASEBAND_VERSION, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "getBasebandVersion", () -> {
+            modemProxy.getBasebandVersion(rr.mSerial);
+        });
     }
 
     @Override
     public void setMute(boolean enableMute, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_MUTE, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " enableMute = " + enableMute);
-            }
-
-            try {
-                voiceProxy.setMute(rr.mSerial, enableMute);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setMute", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("setMute", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_MUTE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enableMute = " + enableMute);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "setMute", () -> {
+            voiceProxy.setMute(rr.mSerial, enableMute);
+        });
     }
 
     @Override
     public void getMute(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_MUTE, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.getMute(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getMute", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("getMute", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_MUTE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "getMute", () -> {
+            voiceProxy.getMute(rr.mSerial);
+        });
     }
 
     @Override
     public void queryCLIP(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_CLIP, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.getClip(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getClip", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("queryCLIP", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
-    }
 
-    /**
-     * @deprecated
-     */
-    @Override
-    @Deprecated
-    public void getPDPContextList(Message result) {
-        getDataCallList(result);
+        RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_CLIP, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "queryCLIP", () -> {
+            voiceProxy.getClip(rr.mSerial);
+        });
     }
 
     @Override
     public void getDataCallList(Message result) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (!dataProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DATA_CALL_LIST, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                dataProxy.getDataCallList(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "getDataCallList", e);
-            }
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("getDataCallList", dataProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
-    }
 
-    // TODO(b/171260715) Remove when HAL definition is removed
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-    }
+        RILRequest rr = obtainRequest(RIL_REQUEST_DATA_CALL_LIST, result, mRILDefaultWorkSource);
 
-    // TODO(b/171260715) Remove when HAL definition is removed
-    @Override
-    public void invokeOemRilRequestStrings(String[] strings, Message result) {
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "getDataCallList", () -> {
+            dataProxy.getDataCallList(rr.mSerial);
+        });
     }
 
     @Override
     public void setSuppServiceNotifications(boolean enable, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " enable = " + enable);
-            }
-
-            try {
-                networkProxy.setSuppServiceNotifications(rr.mSerial, enable);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "setSuppServiceNotifications", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setSuppServiceNotifications", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enable = " + enable);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setSuppServiceNotifications", () -> {
+            networkProxy.setSuppServiceNotifications(rr.mSerial, enable);
+        });
     }
 
     @Override
     public void writeSmsToSim(int status, String smsc, String pdu, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_WRITE_SMS_TO_SIM, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGV) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " " + status);
-            }
-
-            try {
-                messagingProxy.writeSmsToSim(rr.mSerial, status,
-                        RILUtils.convertNullToEmptyString(smsc),
-                        RILUtils.convertNullToEmptyString(pdu));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "writeSmsToSim", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("writeSmsToSim", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_WRITE_SMS_TO_SIM, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGV) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " " + status);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "writeSmsToSim", () -> {
+            messagingProxy.writeSmsToSim(rr.mSerial, status,
+                    RILUtils.convertNullToEmptyString(smsc),
+                    RILUtils.convertNullToEmptyString(pdu));
+        });
     }
 
     @Override
     public void deleteSmsOnSim(int index, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DELETE_SMS_ON_SIM, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGV) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " index = " + index);
-            }
-
-            try {
-                messagingProxy.deleteSmsOnSim(rr.mSerial, index);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "deleteSmsOnSim", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("deleteSmsOnSim", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_DELETE_SMS_ON_SIM, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGV) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " index = " + index);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "deleteSmsOnSim", () -> {
+            messagingProxy.deleteSmsOnSim(rr.mSerial, index);
+        });
     }
 
     @Override
     public void setBandMode(int bandMode, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_BAND_MODE, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " bandMode = " + bandMode);
-            }
-
-            try {
-                networkProxy.setBandMode(rr.mSerial, bandMode);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setBandMode", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setBandMode", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_BAND_MODE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " bandMode = " + bandMode);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setBandMode", () -> {
+            networkProxy.setBandMode(rr.mSerial, bandMode);
+        });
     }
 
     @Override
     public void queryAvailableBandMode(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getAvailableBandModes(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "queryAvailableBandMode", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("queryAvailableBandMode", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "queryAvailableBandMode", () -> {
+            networkProxy.getAvailableBandModes(rr.mSerial);
+        });
     }
 
     @Override
     public void sendEnvelope(String contents, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " contents = " + contents);
-            }
-
-            try {
-                simProxy.sendEnvelope(rr.mSerial, RILUtils.convertNullToEmptyString(contents));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "sendEnvelope", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("sendEnvelope", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " contents = " + contents);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "sendEnvelope", () -> {
+            simProxy.sendEnvelope(rr.mSerial, RILUtils.convertNullToEmptyString(contents));
+        });
     }
 
     @Override
     public void sendTerminalResponse(String contents, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " contents = " + (TelephonyUtils.IS_DEBUGGABLE
-                        ? contents : RILUtils.convertToCensoredTerminalResponse(contents)));
-            }
-
-            try {
-                simProxy.sendTerminalResponseToSim(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(contents));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "sendTerminalResponse", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("sendTerminalResponse", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " contents = " + (TelephonyUtils.IS_DEBUGGABLE
+                    ? contents : RILUtils.convertToCensoredTerminalResponse(contents)));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "sendTerminalResponse", () -> {
+            simProxy.sendTerminalResponseToSim(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(contents));
+        });
     }
 
     @Override
     public void sendEnvelopeWithStatus(String contents, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " contents = " + contents);
-            }
-
-            try {
-                simProxy.sendEnvelopeWithStatus(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(contents));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "sendEnvelopeWithStatus", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("sendEnvelopeWithStatus", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " contents = " + contents);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "sendEnvelopeWithStatus", () -> {
+            simProxy.sendEnvelopeWithStatus(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(contents));
+        });
     }
 
     @Override
     public void explicitCallTransfer(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_EXPLICIT_CALL_TRANSFER, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.explicitCallTransfer(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "explicitCallTransfer", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("explicitCallTransfer", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_EXPLICIT_CALL_TRANSFER, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "explicitCallTransfer", () -> {
+            voiceProxy.explicitCallTransfer(rr.mSerial);
+        });
     }
 
     @Override
     public void setPreferredNetworkType(@PrefNetworkMode int networkType , Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " networkType = " + networkType);
-            }
-            mAllowedNetworkTypesBitmask = RadioAccessFamily.getRafFromNetworkType(networkType);
-            mMetrics.writeSetPreferredNetworkType(mPhoneId, networkType);
-
-            try {
-                networkProxy.setPreferredNetworkTypeBitmap(rr.mSerial, mAllowedNetworkTypesBitmask);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setPreferredNetworkType", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setPreferredNetworkType", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " networkType = " + networkType);
+        }
+        mAllowedNetworkTypesBitmask = RadioAccessFamily.getRafFromNetworkType(networkType);
+        mMetrics.writeSetPreferredNetworkType(mPhoneId, networkType);
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setPreferredNetworkType", () -> {
+            networkProxy.setPreferredNetworkTypeBitmap(rr.mSerial, mAllowedNetworkTypesBitmask);
+        });
     }
 
     @Override
     public void getPreferredNetworkType(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getPreferredNetworkType", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getPreferredNetworkType", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getPreferredNetworkType", () -> {
+            networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial);
+        });
     }
 
     @Override
     public void setAllowedNetworkTypesBitmap(
             @TelephonyManager.NetworkTypeBitMask int networkTypeBitmask, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            if (mHalVersion.get(HAL_SERVICE_NETWORK).less(RADIO_HAL_VERSION_1_6)) {
-                // For older HAL, redirects the call to setPreferredNetworkType.
-                setPreferredNetworkType(
-                        RadioAccessFamily.getNetworkTypeFromRaf(networkTypeBitmask), result);
-                return;
-            }
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-            mAllowedNetworkTypesBitmask = networkTypeBitmask;
-
-            try {
-                networkProxy.setAllowedNetworkTypesBitmap(rr.mSerial, mAllowedNetworkTypesBitmask);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "setAllowedNetworkTypeBitmask", e);
-            }
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).less(RADIO_HAL_VERSION_1_6)) {
+            // For older HAL, redirects the call to setPreferredNetworkType.
+            setPreferredNetworkType(
+                    RadioAccessFamily.getNetworkTypeFromRaf(networkTypeBitmask), result);
+            return;
         }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setAllowedNetworkTypesBitmap", networkProxy, result,
+                RADIO_HAL_VERSION_1_6)) {
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+        mAllowedNetworkTypesBitmask = networkTypeBitmask;
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setAllowedNetworkTypesBitmap", () -> {
+            networkProxy.setAllowedNetworkTypesBitmap(rr.mSerial, mAllowedNetworkTypesBitmask);
+        });
     }
 
     @Override
     public void getAllowedNetworkTypesBitmap(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "getAllowedNetworkTypeBitmask", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getAllowedNetworkTypesBitmap", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getAllowedNetworkTypesBitmap", () -> {
+            networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial);
+        });
     }
 
     @Override
     public void setLocationUpdates(boolean enable, WorkSource workSource, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_LOCATION_UPDATES, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " enable = " + enable);
-            }
-
-            try {
-                networkProxy.setLocationUpdates(rr.mSerial, enable);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setLocationUpdates", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setLocationUpdates", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_LOCATION_UPDATES, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enable = " + enable);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setLocationUpdates", () -> {
+            networkProxy.setLocationUpdates(rr.mSerial, enable);
+        });
     }
 
     /**
@@ -3126,32 +2935,22 @@
      */
     @Override
     public void isNrDualConnectivityEnabled(Message result, WorkSource workSource) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.isNrDualConnectivityEnabled(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "isNrDualConnectivityEnabled", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "isNrDualConnectivityEnabled: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("isNrDualConnectivityEnabled", networkProxy, result,
+                RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "isNrDualConnectivityEnabled", () -> {
+            networkProxy.isNrDualConnectivityEnabled(rr.mSerial);
+        });
     }
 
     /**
@@ -3168,30 +2967,23 @@
     @Override
     public void setNrDualConnectivityState(int nrDualConnectivityState, Message result,
             WorkSource workSource) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " enable = " + nrDualConnectivityState);
-            }
-
-            try {
-                networkProxy.setNrDualConnectivityState(rr.mSerial, (byte) nrDualConnectivityState);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "enableNrDualConnectivity", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "enableNrDualConnectivity: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setNrDualConnectivityState", networkProxy, result,
+                RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enable = " + nrDualConnectivityState);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setNrDualConnectivityState", () -> {
+            networkProxy.setNrDualConnectivityState(rr.mSerial, (byte) nrDualConnectivityState);
+        });
     }
 
     private void setVoNrEnabled(boolean enabled) {
@@ -3207,30 +2999,27 @@
      */
     @Override
     public void isVoNrEnabled(Message result, WorkSource workSource) {
-
-        if (mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-            if (!voiceProxy.isEmpty()) {
-                RILRequest rr = obtainRequest(RIL_REQUEST_IS_VONR_ENABLED , result,
-                        getDefaultWorkSourceIfInvalid(workSource));
-
-                if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-                }
-
-                try {
-                    voiceProxy.isVoNrEnabled(rr.mSerial);
-                } catch (RemoteException | RuntimeException e) {
-                    handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "isVoNrEnabled", e);
-                }
-            }
-        } else {
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        // Send null result so errors aren't sent in canMakeRequest
+        if (!canMakeRequest("isVoNrEnabled", voiceProxy, null, RADIO_HAL_VERSION_2_0)) {
             boolean isEnabled = isVoNrEnabled();
             if (result != null) {
                 AsyncResult.forMessage(result, isEnabled, null);
                 result.sendToTarget();
             }
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_IS_VONR_ENABLED, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "isVoNrEnabled", () -> {
+            voiceProxy.isVoNrEnabled(rr.mSerial);
+        });
     }
 
     /**
@@ -3240,24 +3029,9 @@
     @Override
     public void setVoNrEnabled(boolean enabled, Message result, WorkSource workSource) {
         setVoNrEnabled(enabled);
-
-        if (mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-            if (!voiceProxy.isEmpty()) {
-                RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_VONR, result,
-                        getDefaultWorkSourceIfInvalid(workSource));
-
-                if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-                }
-
-                try {
-                    voiceProxy.setVoNrEnabled(rr.mSerial, enabled);
-                } catch (RemoteException | RuntimeException e) {
-                    handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setVoNrEnabled", e);
-                }
-            }
-        } else {
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        // Send null result so errors aren't sent in canMakeRequest
+        if (!canMakeRequest("setVoNrEnabled", voiceProxy, null, RADIO_HAL_VERSION_2_0)) {
             /* calling a query api to let HAL know that VoNREnabled state is updated.
                This is a work around as new AIDL API is not allowed for older HAL version devices.
                HAL can check the value of PROPERTY_IS_VONR_ENABLED property to determine
@@ -3268,858 +3042,839 @@
                 AsyncResult.forMessage(result, null, null);
                 result.sendToTarget();
             }
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_VONR, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "setVoNrEnabled", () -> {
+            voiceProxy.setVoNrEnabled(rr.mSerial, enabled);
+        });
     }
 
     @Override
     public void setCdmaSubscriptionSource(int cdmaSubscription, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " cdmaSubscription = " + cdmaSubscription);
-            }
-
-            try {
-                simProxy.setCdmaSubscriptionSource(rr.mSerial, cdmaSubscription);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setCdmaSubscriptionSource", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("setCdmaSubscriptionSource", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " cdmaSubscription = " + cdmaSubscription);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "setCdmaSubscriptionSource", () -> {
+            simProxy.setCdmaSubscriptionSource(rr.mSerial, cdmaSubscription);
+        });
     }
 
     @Override
     public void queryCdmaRoamingPreference(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getCdmaRoamingPreference(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "queryCdmaRoamingPreference", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("queryCdmaRoamingPreference", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "queryCdmaRoamingPreference", () -> {
+            networkProxy.getCdmaRoamingPreference(rr.mSerial);
+        });
     }
 
     @Override
     public void setCdmaRoamingPreference(int cdmaRoamingType, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " cdmaRoamingType = " + cdmaRoamingType);
-            }
-
-            try {
-                networkProxy.setCdmaRoamingPreference(rr.mSerial, cdmaRoamingType);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setCdmaRoamingPreference", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setCdmaRoamingPreference", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " cdmaRoamingType = " + cdmaRoamingType);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setCdmaRoamingPreference", () -> {
+            networkProxy.setCdmaRoamingPreference(rr.mSerial, cdmaRoamingType);
+        });
     }
 
     @Override
     public void queryTTYMode(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_TTY_MODE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.getTtyMode(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getTtyMode", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("queryTTYMode", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_TTY_MODE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "queryTTYMode", () -> {
+            voiceProxy.getTtyMode(rr.mSerial);
+        });
     }
 
     @Override
     public void setTTYMode(int ttyMode, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_TTY_MODE, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " ttyMode = " + ttyMode);
-            }
-
-            try {
-                voiceProxy.setTtyMode(rr.mSerial, ttyMode);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setTtyMode", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("setTTYMode", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_TTY_MODE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " ttyMode = " + ttyMode);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "setTTYMode", () -> {
+            voiceProxy.setTtyMode(rr.mSerial, ttyMode);
+        });
     }
 
     @Override
     public void setPreferredVoicePrivacy(boolean enable, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " enable = " + enable);
-            }
-
-            try {
-                voiceProxy.setPreferredVoicePrivacy(rr.mSerial, enable);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setPreferredVoicePrivacy", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("setPreferredVoicePrivacy", voiceProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enable = " + enable);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "setPreferredVoicePrivacy", () -> {
+            voiceProxy.setPreferredVoicePrivacy(rr.mSerial, enable);
+        });
     }
 
     @Override
     public void getPreferredVoicePrivacy(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE,
-                    result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.getPreferredVoicePrivacy(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getPreferredVoicePrivacy", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("getPreferredVoicePrivacy", voiceProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE,
+                result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "getPreferredVoicePrivacy", () -> {
+            voiceProxy.getPreferredVoicePrivacy(rr.mSerial);
+        });
     }
 
     @Override
     public void sendCDMAFeatureCode(String featureCode, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_FLASH, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " featureCode = " + Rlog.pii(RILJ_LOG_TAG, featureCode));
-            }
-
-            try {
-                voiceProxy.sendCdmaFeatureCode(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(featureCode));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendCdmaFeatureCode", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("sendCDMAFeatureCode", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_FLASH, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " featureCode = " + Rlog.pii(RILJ_LOG_TAG, featureCode));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "sendCDMAFeatureCode", () -> {
+            voiceProxy.sendCdmaFeatureCode(rr.mSerial,
+                    RILUtils.convertNullToEmptyString(featureCode));
+        });
     }
 
     @Override
     public void sendBurstDtmf(String dtmfString, int on, int off, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_BURST_DTMF, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " dtmfString = " + dtmfString + " on = " + on + " off = " + off);
-            }
-
-            try {
-                voiceProxy.sendBurstDtmf(rr.mSerial, RILUtils.convertNullToEmptyString(dtmfString),
-                        on, off);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendBurstDtmf", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("sendBurstDtmf", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_BURST_DTMF, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " dtmfString = " + dtmfString + " on = " + on + " off = " + off);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "sendBurstDtmf", () -> {
+            voiceProxy.sendBurstDtmf(rr.mSerial, RILUtils.convertNullToEmptyString(dtmfString),
+                    on, off);
+        });
     }
 
     @Override
     public void sendCdmaSMSExpectMore(byte[] pdu, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE, result,
-                    mRILDefaultWorkSource);
-
-            // Do not log function arg for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                messagingProxy.sendCdmaSmsExpectMore(rr.mSerial, pdu);
-                if (mHalVersion.get(HAL_SERVICE_MESSAGING).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
-                    mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_CDMA,
-                            SmsSession.Event.Format.SMS_FORMAT_3GPP2,
-                            getOutgoingSmsMessageId(result));
-                }
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendCdmaSMSExpectMore", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("sendCdmaSMSExpectMore", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE, result,
+                mRILDefaultWorkSource);
+
+        // Do not log function arg for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "sendCdmaSMSExpectMore", () -> {
+            messagingProxy.sendCdmaSmsExpectMore(rr.mSerial, pdu);
+            if (mHalVersion.get(HAL_SERVICE_MESSAGING).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_CDMA,
+                        SmsSession.Event.Format.SMS_FORMAT_3GPP2,
+                        getOutgoingSmsMessageId(result));
+            }
+        });
     }
 
     @Override
     public void sendCdmaSms(byte[] pdu, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SEND_SMS, result, mRILDefaultWorkSource);
-
-            // Do not log function arg for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                messagingProxy.sendCdmaSms(rr.mSerial, pdu);
-                mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_CDMA,
-                        SmsSession.Event.Format.SMS_FORMAT_3GPP2, getOutgoingSmsMessageId(result));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendCdmaSms", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("sendCdmaSms", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SEND_SMS, result, mRILDefaultWorkSource);
+
+        // Do not log function arg for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "sendCdmaSms", () -> {
+            messagingProxy.sendCdmaSms(rr.mSerial, pdu);
+            mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_CDMA,
+                    SmsSession.Event.Format.SMS_FORMAT_3GPP2, getOutgoingSmsMessageId(result));
+        });
     }
 
     @Override
     public void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " success = " + success + " cause = " + cause);
-            }
-
-            try {
-                messagingProxy.acknowledgeLastIncomingCdmaSms(rr.mSerial, success, cause);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
-                        "acknowledgeLastIncomingCdmaSms", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("acknowledgeLastIncomingCdmaSms", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " success = " + success + " cause = " + cause);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "acknowledgeLastIncomingCdmaSms",
+                () -> {
+                    messagingProxy.acknowledgeLastIncomingCdmaSms(rr.mSerial, success, cause);
+                });
     }
 
     @Override
     public void getGsmBroadcastConfig(Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GSM_GET_BROADCAST_CONFIG, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                messagingProxy.getGsmBroadcastConfig(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getGsmBroadcastConfig", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("getGsmBroadcastConfig", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GSM_GET_BROADCAST_CONFIG, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "getGsmBroadcastConfig", () -> {
+            messagingProxy.getGsmBroadcastConfig(rr.mSerial);
+        });
     }
 
     @Override
     public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GSM_SET_BROADCAST_CONFIG, result,
-                    mRILDefaultWorkSource);
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("setGsmBroadcastConfig", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
+        }
 
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " with " + config.length + " configs : ");
-                for (int i = 0; i < config.length; i++) {
-                    riljLog(config[i].toString());
-                }
-            }
+        RILRequest rr = obtainRequest(RIL_REQUEST_GSM_SET_BROADCAST_CONFIG, result,
+                mRILDefaultWorkSource);
 
-            try {
-                messagingProxy.setGsmBroadcastConfig(rr.mSerial, config);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setGsmBroadcastConfig", e);
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " with " + config.length + " configs : ");
+            for (int i = 0; i < config.length; i++) {
+                riljLog(config[i].toString());
             }
         }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "setGsmBroadcastConfig", () -> {
+            messagingProxy.setGsmBroadcastConfig(rr.mSerial, config);
+        });
     }
 
     @Override
     public void setGsmBroadcastActivation(boolean activate, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GSM_BROADCAST_ACTIVATION, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " activate = " + activate);
-            }
-
-            try {
-                messagingProxy.setGsmBroadcastActivation(rr.mSerial, activate);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
-                        "setGsmBroadcastActivation", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("setGsmBroadcastActivation", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GSM_BROADCAST_ACTIVATION, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " activate = " + activate);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "setGsmBroadcastActivation", () -> {
+            messagingProxy.setGsmBroadcastActivation(rr.mSerial, activate);
+        });
     }
 
     @Override
     public void getCdmaBroadcastConfig(Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                messagingProxy.getCdmaBroadcastConfig(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getCdmaBroadcastConfig", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("getCdmaBroadcastConfig", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "getCdmaBroadcastConfig", () -> {
+            messagingProxy.getCdmaBroadcastConfig(rr.mSerial);
+        });
     }
 
     @Override
     public void setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG, result,
-                    mRILDefaultWorkSource);
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("setCdmaBroadcastConfig", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
+        }
 
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " with " + configs.length + " configs : ");
-                for (CdmaSmsBroadcastConfigInfo config : configs) {
-                    riljLog(config.toString());
-                }
-            }
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG, result,
+                mRILDefaultWorkSource);
 
-            try {
-                messagingProxy.setCdmaBroadcastConfig(rr.mSerial, configs);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setCdmaBroadcastConfig", e);
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " with " + configs.length + " configs : ");
+            for (CdmaSmsBroadcastConfigInfo config : configs) {
+                riljLog(config.toString());
             }
         }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "setCdmaBroadcastConfig", () -> {
+            messagingProxy.setCdmaBroadcastConfig(rr.mSerial, configs);
+        });
     }
 
     @Override
     public void setCdmaBroadcastActivation(boolean activate, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_BROADCAST_ACTIVATION, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " activate = " + activate);
-            }
-
-            try {
-                messagingProxy.setCdmaBroadcastActivation(rr.mSerial, activate);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
-                        "setCdmaBroadcastActivation", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("setCdmaBroadcastActivation", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_BROADCAST_ACTIVATION, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " activate = " + activate);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "setCdmaBroadcastActivation", () -> {
+            messagingProxy.setCdmaBroadcastActivation(rr.mSerial, activate);
+        });
     }
 
     @Override
     public void getCDMASubscription(Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SUBSCRIPTION, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.getCdmaSubscription(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getCdmaSubscription", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("getCDMASubscription", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SUBSCRIPTION, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "getCDMASubscription", () -> {
+            simProxy.getCdmaSubscription(rr.mSerial);
+        });
     }
 
     @Override
     public void writeSmsToRuim(int status, byte[] pdu, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGV) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " status = " + status);
-            }
-
-            try {
-                messagingProxy.writeSmsToRuim(rr.mSerial, status, pdu);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "writeSmsToRuim", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("writeSmsToRuim", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGV) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " status = " + status);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "writeSmsToRuim", () -> {
+            messagingProxy.writeSmsToRuim(rr.mSerial, status, pdu);
+        });
     }
 
     @Override
     public void deleteSmsOnRuim(int index, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGV) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " index = " + index);
-            }
-
-            try {
-                messagingProxy.deleteSmsOnRuim(rr.mSerial, index);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "deleteSmsOnRuim", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("deleteSmsOnRuim", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGV) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " index = " + index);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "deleteSmsOnRuim", () -> {
+            messagingProxy.deleteSmsOnRuim(rr.mSerial, index);
+        });
     }
 
     @Override
     public void getDeviceIdentity(Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DEVICE_IDENTITY, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                modemProxy.getDeviceIdentity(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getDeviceIdentity", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("getDeviceIdentity", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_DEVICE_IDENTITY, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "getDeviceIdentity", () -> {
+            modemProxy.getDeviceIdentity(rr.mSerial);
+        });
     }
 
     @Override
     public void getImei(Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (modemProxy.isEmpty()) {
-            if (RILJ_LOGD) {
-                Rlog.e(RILJ_LOG_TAG, "getImei: modemProxy is Empty");
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("getImei", modemProxy, result, RADIO_HAL_VERSION_2_1)) {
             return;
         }
-        if (mHalVersion.get(HAL_SERVICE_MODEM).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_DEVICE_IMEI, result,
-                    mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
+        RILRequest rr = obtainRequest(RIL_REQUEST_DEVICE_IMEI, result, mRILDefaultWorkSource);
 
-            try {
-                modemProxy.getImei(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getImei", e);
-            }
-        }  else {
-            if (RILJ_LOGD) {
-                Rlog.e(RILJ_LOG_TAG, "getImei: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
         }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "getImei", () -> {
+            modemProxy.getImei(rr.mSerial);
+        });
     }
 
     @Override
     public void exitEmergencyCallbackMode(Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.exitEmergencyCallbackMode(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "exitEmergencyCallbackMode", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("exitEmergencyCallbackMode", voiceProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "exitEmergencyCallbackMode", () -> {
+            voiceProxy.exitEmergencyCallbackMode(rr.mSerial);
+        });
     }
 
     @Override
     public void getSmscAddress(Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SMSC_ADDRESS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                messagingProxy.getSmscAddress(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getSmscAddress", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("getSmscAddress", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_SMSC_ADDRESS, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "getSmscAddress", () -> {
+            messagingProxy.getSmscAddress(rr.mSerial);
+        });
     }
 
     @Override
     public void setSmscAddress(String address, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_SMSC_ADDRESS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " address = " + address);
-            }
-
-            try {
-                messagingProxy.setSmscAddress(rr.mSerial,
-                        RILUtils.convertNullToEmptyString(address));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setSmscAddress", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("setSmscAddress", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SMSC_ADDRESS, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " address = " + address);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "setSmscAddress", () -> {
+            messagingProxy.setSmscAddress(rr.mSerial, RILUtils.convertNullToEmptyString(address));
+        });
     }
 
     @Override
     public void reportSmsMemoryStatus(boolean available, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_REPORT_SMS_MEMORY_STATUS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " available = " + available);
-            }
-
-            try {
-                messagingProxy.reportSmsMemoryStatus(rr.mSerial, available);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "reportSmsMemoryStatus", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("reportSmsMemoryStatus", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_REPORT_SMS_MEMORY_STATUS, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " available = " + available);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "reportSmsMemoryStatus", () -> {
+            messagingProxy.reportSmsMemoryStatus(rr.mSerial, available);
+        });
     }
 
     @Override
     public void reportStkServiceIsRunning(Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.reportStkServiceIsRunning(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "reportStkServiceIsRunning", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("reportStkServiceIsRunning", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "reportStkServiceIsRunning", () -> {
+            simProxy.reportStkServiceIsRunning(rr.mSerial);
+        });
     }
 
     @Override
     public void getCdmaSubscriptionSource(Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.getCdmaSubscriptionSource(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getCdmaSubscriptionSource", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("getCdmaSubscriptionSource", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "getCdmaSubscriptionSource", () -> {
+            simProxy.getCdmaSubscriptionSource(rr.mSerial);
+        });
     }
 
     @Override
     public void acknowledgeIncomingGsmSmsWithPdu(boolean success, String ackPdu, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " success = " + success);
-            }
-
-            try {
-                messagingProxy.acknowledgeIncomingGsmSmsWithPdu(rr.mSerial, success,
-                        RILUtils.convertNullToEmptyString(ackPdu));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
-                        "acknowledgeIncomingGsmSmsWithPdu", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("acknowledgeIncomingGsmSmsWithPdu", messagingProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " success = " + success);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "acknowledgeIncomingGsmSmsWithPdu",
+                () -> {
+                    messagingProxy.acknowledgeIncomingGsmSmsWithPdu(rr.mSerial, success,
+                            RILUtils.convertNullToEmptyString(ackPdu));
+                });
     }
 
     @Override
     public void getVoiceRadioTechnology(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_VOICE_RADIO_TECH, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getVoiceRadioTechnology(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getVoiceRadioTechnology", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getVoiceRadioTechnology", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_VOICE_RADIO_TECH, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getVoiceRadioTechnology", () -> {
+            networkProxy.getVoiceRadioTechnology(rr.mSerial);
+        });
     }
 
     @Override
     public void getCellInfoList(Message result, WorkSource workSource) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_CELL_INFO_LIST, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getCellInfoList(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getCellInfoList", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getCellInfoList", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_CELL_INFO_LIST, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getCellInfoList", () -> {
+            networkProxy.getCellInfoList(rr.mSerial);
+        });
     }
 
     @Override
     public void setCellInfoListRate(int rateInMillis, Message result, WorkSource workSource) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " rateInMillis = " + rateInMillis);
-            }
-
-            try {
-                networkProxy.setCellInfoListRate(rr.mSerial, rateInMillis);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setCellInfoListRate", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setCellInfoListRate", networkProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " rateInMillis = " + rateInMillis);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setCellInfoListRate", () -> {
+            networkProxy.setCellInfoListRate(rr.mSerial, rateInMillis);
+        });
     }
 
     @Override
-    public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, Message result) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (!dataProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_INITIAL_ATTACH_APN, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + dataProfile);
-            }
-
-            try {
-                dataProxy.setInitialAttachApn(rr.mSerial, dataProfile, isRoaming);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setInitialAttachApn", e);
-            }
+    public void setInitialAttachApn(DataProfile dataProfile, Message result) {
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("setInitialAttachApn", dataProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_INITIAL_ATTACH_APN, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + dataProfile);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "setInitialAttachApn", () -> {
+            dataProxy.setInitialAttachApn(rr.mSerial, dataProfile);
+        });
     }
 
     @Override
     public void getImsRegistrationState(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_IMS_REGISTRATION_STATE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getImsRegistrationState(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getImsRegistrationState", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getImsRegistrationState", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_IMS_REGISTRATION_STATE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getImsRegistrationState", () -> {
+            networkProxy.getImsRegistrationState(rr.mSerial);
+        });
     }
 
     @Override
     public void sendImsGsmSms(String smscPdu, String pdu, int retry, int messageRef,
             Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_IMS_SEND_SMS, result, mRILDefaultWorkSource);
-
-            // Do not log function args for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                messagingProxy.sendImsSms(rr.mSerial, smscPdu, pdu, null, retry, messageRef);
-                mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS,
-                        SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendImsGsmSms", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("sendImsGsmSms", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_IMS_SEND_SMS, result, mRILDefaultWorkSource);
+
+        // Do not log function args for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "sendImsGsmSms", () -> {
+            messagingProxy.sendImsSms(rr.mSerial, smscPdu, pdu, null, retry, messageRef);
+            mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS,
+                    SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result));
+        });
     }
 
     @Override
     public void sendImsCdmaSms(byte[] pdu, int retry, int messageRef, Message result) {
-        RadioMessagingProxy messagingProxy =
-                getRadioServiceProxy(RadioMessagingProxy.class, result);
-        if (!messagingProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_IMS_SEND_SMS, result, mRILDefaultWorkSource);
-
-            // Do not log function args for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                messagingProxy.sendImsSms(rr.mSerial, null, null, pdu, retry, messageRef);
-                mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS,
-                        SmsSession.Event.Format.SMS_FORMAT_3GPP2, getOutgoingSmsMessageId(result));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendImsCdmaSms", e);
-            }
+        RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
+        if (!canMakeRequest("sendImsCdmaSms", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_IMS_SEND_SMS, result, mRILDefaultWorkSource);
+
+        // Do not log function args for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MESSAGING, rr, "sendImsCdmaSms", () -> {
+            messagingProxy.sendImsSms(rr.mSerial, null, null, pdu, retry, messageRef);
+            mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS,
+                    SmsSession.Event.Format.SMS_FORMAT_3GPP2, getOutgoingSmsMessageId(result));
+        });
     }
 
     @Override
     public void iccTransmitApduBasicChannel(int cla, int instruction, int p1, int p2, int p3,
             String data, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC, result,
-                    mRILDefaultWorkSource);
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("iccTransmitApduBasicChannel", simProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
+        }
 
-            if (RILJ_LOGD) {
-                if (TelephonyUtils.IS_DEBUGGABLE) {
-                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                            + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction)
-                            + String.format(" p1 = 0x%02X p2 = 0x%02X p3 = 0x%02X", p1, p2, p3)
-                            + " data = " + data);
-                } else {
-                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-                }
-            }
+        RILRequest rr = obtainRequest(RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC, result,
+                mRILDefaultWorkSource);
 
-            try {
-                simProxy.iccTransmitApduBasicChannel(
-                        rr.mSerial, cla, instruction, p1, p2, p3, data);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccTransmitApduBasicChannel", e);
+        if (RILJ_LOGD) {
+            if (TelephonyUtils.IS_DEBUGGABLE) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction)
+                        + String.format(" p1 = 0x%02X p2 = 0x%02X p3 = 0x%02X", p1, p2, p3)
+                        + " data = " + data);
+            } else {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
         }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "iccTransmitApduBasicChannel", () -> {
+            simProxy.iccTransmitApduBasicChannel(rr.mSerial, cla, instruction, p1, p2, p3, data);
+        });
     }
 
     @Override
     public void iccOpenLogicalChannel(String aid, int p2, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SIM_OPEN_CHANNEL, result,
-                    mRILDefaultWorkSource);
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("iccOpenLogicalChannel", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
+        }
 
-            if (RILJ_LOGD) {
-                if (TelephonyUtils.IS_DEBUGGABLE) {
-                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                            + " aid = " + aid + " p2 = " + p2);
-                } else {
-                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-                }
-            }
+        RILRequest rr = obtainRequest(RIL_REQUEST_SIM_OPEN_CHANNEL, result, mRILDefaultWorkSource);
 
-            try {
-                simProxy.iccOpenLogicalChannel(rr.mSerial, RILUtils.convertNullToEmptyString(aid),
-                        p2);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccOpenLogicalChannel", e);
+        if (RILJ_LOGD) {
+            if (TelephonyUtils.IS_DEBUGGABLE) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " aid = " + aid + " p2 = " + p2);
+            } else {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
         }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "iccOpenLogicalChannel", () -> {
+            simProxy.iccOpenLogicalChannel(rr.mSerial, RILUtils.convertNullToEmptyString(aid), p2);
+        });
     }
 
     @Override
     public void iccCloseLogicalChannel(int channel, boolean isEs10, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SIM_CLOSE_CHANNEL, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " channel = " + channel + " isEs10 = " + isEs10);
-            }
-            try {
-                simProxy.iccCloseLogicalChannel(rr.mSerial, channel, isEs10);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccCloseLogicalChannel", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("iccCloseLogicalChannel", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SIM_CLOSE_CHANNEL, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " channel = " + channel + " isEs10 = " + isEs10);
+        }
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "iccCloseLogicalChannel", () -> {
+            simProxy.iccCloseLogicalChannel(rr.mSerial, channel, isEs10);
+        });
     }
 
     @Override
@@ -4130,351 +3885,276 @@
                     "Invalid channel in iccTransmitApduLogicalChannel: " + channel);
         }
 
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL, result,
-                    mRILDefaultWorkSource);
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("iccTransmitApduLogicalChannel", simProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
+        }
 
-            if (RILJ_LOGD) {
-                if (TelephonyUtils.IS_DEBUGGABLE) {
-                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                            + String.format(" channel = %d", channel)
-                            + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction)
-                            + String.format(" p1 = 0x%02X p2 = 0x%02X p3 = 0x%02X", p1, p2, p3)
-                            + " isEs10Command = " + isEs10Command
-                            + " data = " + data);
-                } else {
-                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-                }
-            }
+        RILRequest rr = obtainRequest(RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL, result,
+                mRILDefaultWorkSource);
 
-            try {
-                simProxy.iccTransmitApduLogicalChannel(
-                        rr.mSerial, channel, cla, instruction, p1, p2, p3, data, isEs10Command);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccTransmitApduLogicalChannel", e);
+        if (RILJ_LOGD) {
+            if (TelephonyUtils.IS_DEBUGGABLE) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + String.format(" channel = %d", channel)
+                        + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction)
+                        + String.format(" p1 = 0x%02X p2 = 0x%02X p3 = 0x%02X", p1, p2, p3)
+                        + " isEs10Command = " + isEs10Command
+                        + " data = " + data);
+            } else {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
         }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "iccTransmitApduLogicalChannel", () -> {
+            simProxy.iccTransmitApduLogicalChannel(rr.mSerial, channel, cla, instruction, p1, p2,
+                    p3, data, isEs10Command);
+        });
     }
 
     @Override
     public void nvReadItem(int itemID, Message result, WorkSource workSource) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_NV_READ_ITEM, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " itemId = " + itemID);
-            }
-
-            try {
-                modemProxy.nvReadItem(rr.mSerial, itemID);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvReadItem", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("nvReadItem", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_NV_READ_ITEM, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " itemId = " + itemID);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "nvReadItem", () -> {
+            modemProxy.nvReadItem(rr.mSerial, itemID);
+        });
     }
 
     @Override
     public void nvWriteItem(int itemId, String itemValue, Message result, WorkSource workSource) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_NV_WRITE_ITEM, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " itemId = " + itemId + " itemValue = " + itemValue);
-            }
-
-            try {
-                modemProxy.nvWriteItem(rr.mSerial, itemId,
-                        RILUtils.convertNullToEmptyString(itemValue));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvWriteItem", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("nvWriteItem", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_NV_WRITE_ITEM, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " itemId = " + itemId + " itemValue = " + itemValue);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "nvWriteItem", () -> {
+            modemProxy.nvWriteItem(rr.mSerial, itemId,
+                    RILUtils.convertNullToEmptyString(itemValue));
+        });
     }
 
     @Override
     public void nvWriteCdmaPrl(byte[] preferredRoamingList, Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_NV_WRITE_CDMA_PRL, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " PreferredRoamingList = 0x"
-                        + IccUtils.bytesToHexString(preferredRoamingList));
-            }
-
-            try {
-                modemProxy.nvWriteCdmaPrl(rr.mSerial, preferredRoamingList);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvWriteCdmaPrl", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("nvWriteCdmaPrl", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_NV_WRITE_CDMA_PRL, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " PreferredRoamingList = 0x"
+                    + IccUtils.bytesToHexString(preferredRoamingList));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "nvWriteCdmaPrl", () -> {
+            modemProxy.nvWriteCdmaPrl(rr.mSerial, preferredRoamingList);
+        });
     }
 
     @Override
     public void nvResetConfig(int resetType, Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_NV_RESET_CONFIG, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " resetType = " + resetType);
-            }
-
-            try {
-                modemProxy.nvResetConfig(rr.mSerial, resetType);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvResetConfig", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("nvResetConfig", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_NV_RESET_CONFIG, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " resetType = " + resetType);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "nvResetConfig", () -> {
+            modemProxy.nvResetConfig(rr.mSerial, resetType);
+        });
     }
 
     @Override
     public void setUiccSubscription(int slotId, int appIndex, int subId, int subStatus,
             Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_UICC_SUBSCRIPTION, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " slot = " + slotId + " appIndex = " + appIndex
-                        + " subId = " + subId + " subStatus = " + subStatus);
-            }
-
-            try {
-                simProxy.setUiccSubscription(rr.mSerial, slotId, appIndex, subId, subStatus);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setUiccSubscription", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("setUiccSubscription", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
-    }
 
-    /**
-     * Whether the device modem supports reporting the EID in either the slot or card status or
-     * through ATR.
-     * @return true if the modem supports EID.
-     */
-    @Override
-    public boolean supportsEid() {
-        // EID should be supported as long as HAL >= 1.2.
-        //  - in HAL 1.2 we have EID through ATR
-        //  - in later HAL versions we also have EID through slot / card status.
-        return mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2);
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_UICC_SUBSCRIPTION, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " slot = " + slotId + " appIndex = " + appIndex
+                    + " subId = " + subId + " subStatus = " + subStatus);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "setUiccSubscription", () -> {
+            simProxy.setUiccSubscription(rr.mSerial, slotId, appIndex, subId, subStatus);
+        });
     }
 
     @Override
     public void setDataAllowed(boolean allowed, Message result) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (!dataProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ALLOW_DATA, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " allowed = " + allowed);
-            }
-
-            try {
-                dataProxy.setDataAllowed(rr.mSerial, allowed);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataAllowed", e);
-            }
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("setDataAllowed", dataProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ALLOW_DATA, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " allowed = " + allowed);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "setDataAllowed", () -> {
+            dataProxy.setDataAllowed(rr.mSerial, allowed);
+        });
     }
 
     @Override
     public void getHardwareConfig(Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_HARDWARE_CONFIG, result,
-                    mRILDefaultWorkSource);
-
-            // Do not log function args for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                modemProxy.getHardwareConfig(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getHardwareConfig", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("getHardwareConfig", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_HARDWARE_CONFIG, result,
+                mRILDefaultWorkSource);
+
+        // Do not log function args for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "getHardwareConfig", () -> {
+            modemProxy.getHardwareConfig(rr.mSerial);
+        });
     }
 
     @Override
     public void requestIccSimAuthentication(int authContext, String data, String aid,
             Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SIM_AUTHENTICATION, result,
-                    mRILDefaultWorkSource);
-
-            // Do not log function args for privacy
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.requestIccSimAuthentication(rr.mSerial, authContext,
-                        RILUtils.convertNullToEmptyString(data),
-                        RILUtils.convertNullToEmptyString(aid));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "requestIccSimAuthentication", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("requestIccSimAuthentication", simProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SIM_AUTHENTICATION, result,
+                mRILDefaultWorkSource);
+
+        // Do not log function args for privacy
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "requestIccSimAuthentication", () -> {
+            simProxy.requestIccSimAuthentication(rr.mSerial, authContext,
+                    RILUtils.convertNullToEmptyString(data),
+                    RILUtils.convertNullToEmptyString(aid));
+        });
     }
 
     @Override
-    public void setDataProfile(DataProfile[] dps, boolean isRoaming, Message result) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (!dataProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_DATA_PROFILE, result,
-                    mRILDefaultWorkSource);
+    public void setDataProfile(DataProfile[] dps, Message result) {
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("setDataProfile", dataProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
+        }
 
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " with data profiles : ");
-                for (DataProfile profile : dps) {
-                    riljLog(profile.toString());
-                }
-            }
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_DATA_PROFILE, result, mRILDefaultWorkSource);
 
-            try {
-                dataProxy.setDataProfile(rr.mSerial, dps, isRoaming);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataProfile", e);
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " with data profiles : ");
+            for (DataProfile profile : dps) {
+                riljLog(profile.toString());
             }
         }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "setDataProfile", () -> {
+            dataProxy.setDataProfile(rr.mSerial, dps);
+        });
     }
 
     @Override
     public void requestShutdown(Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SHUTDOWN, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                modemProxy.requestShutdown(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "requestShutdown", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("requestShutdown", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SHUTDOWN, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "requestShutdown", () -> {
+            modemProxy.requestShutdown(rr.mSerial);
+        });
     }
 
     @Override
     public void getRadioCapability(Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_RADIO_CAPABILITY, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                modemProxy.getRadioCapability(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getRadioCapability", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("getRadioCapability", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_RADIO_CAPABILITY, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "getRadioCapability", () -> {
+            modemProxy.getRadioCapability(rr.mSerial);
+        });
     }
 
     @Override
     public void setRadioCapability(RadioCapability rc, Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_RADIO_CAPABILITY, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " RadioCapability = " + rc.toString());
-            }
-
-            try {
-                modemProxy.setRadioCapability(rr.mSerial, rc);
-            } catch (Exception e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "setRadioCapability", e);
-            }
-        }
-    }
-
-    @Override
-    public void startLceService(int reportIntervalMs, boolean pullMode, Message result) {
-        if (mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
-            // We have a 1.2 or later radio, so the LCE 1.0 LCE service control path is unused.
-            // Instead the LCE functionality is always-on and provides unsolicited indications.
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startLceService: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("setRadioCapability", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
         }
 
-        IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_START_LCE, result, mRILDefaultWorkSource);
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_RADIO_CAPABILITY, result,
+                mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " reportIntervalMs = " + reportIntervalMs + " pullMode = " + pullMode);
-            }
-
-            try {
-                radioProxy.startLceService(rr.mSerial, reportIntervalMs, pullMode);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "startLceService", e);
-            }
-        }
-    }
-
-    @Override
-    public void stopLceService(Message result) {
-        if (mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
-            // We have a 1.2 or later radio, so the LCE 1.0 LCE service control is unused.
-            // Instead the LCE functionality is always-on and provides unsolicited indications.
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "stopLceService: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
-            return;
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " RadioCapability = " + rc.toString());
         }
 
-        IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_STOP_LCE, result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                radioProxy.stopLceService(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "stopLceService", e);
-            }
-        }
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "setRadioCapability", () -> {
+            modemProxy.setRadioCapability(rr.mSerial, rc);
+        });
     }
 
     /**
@@ -4489,96 +4169,45 @@
     @Override
     public void setDataThrottling(Message result, WorkSource workSource, int dataThrottlingAction,
             long completionWindowMillis) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (dataProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_DATA_THROTTLING, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> "
-                        + RILUtils.requestToString(rr.mRequest)
-                        + " dataThrottlingAction = " + dataThrottlingAction
-                        + " completionWindowMillis " + completionWindowMillis);
-            }
-
-            try {
-                dataProxy.setDataThrottling(rr.mSerial, (byte) dataThrottlingAction,
-                        completionWindowMillis);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataThrottling", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "setDataThrottling: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
-        }
-    }
-
-    /**
-     * This will only be called if the LCE service is started in PULL mode, which is
-     * only enabled when using Radio HAL versions 1.1 and earlier.
-     *
-     * It is still possible for vendors to override this behavior and use the 1.1 version
-     * of LCE; however, this is strongly discouraged and this functionality will be removed
-     * when HAL 1.x support is dropped.
-     *
-     * @deprecated HAL 1.2 and later use an always-on LCE that relies on indications.
-     */
-    @Deprecated
-    @Override
-    public void pullLceData(Message result) {
-        if (mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
-            // We have a 1.2 or later radio, so the LCE 1.0 LCE service control path is unused.
-            // Instead the LCE functionality is always-on and provides unsolicited indications.
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "pullLceData: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("setDataThrottling", dataProxy, result, RADIO_HAL_VERSION_1_6)) {
             return;
         }
 
-        IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_PULL_LCEDATA, result, mRILDefaultWorkSource);
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_DATA_THROTTLING, result,
+                getDefaultWorkSourceIfInvalid(workSource));
 
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                radioProxy.pullLceData(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "pullLceData", e);
-            }
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " dataThrottlingAction = " + dataThrottlingAction
+                    + " completionWindowMillis " + completionWindowMillis);
         }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "setDataThrottling", () -> {
+            dataProxy.setDataThrottling(rr.mSerial, (byte) dataThrottlingAction,
+                    completionWindowMillis);
+        });
     }
 
     @Override
     public void getModemActivityInfo(Message result, WorkSource workSource) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_ACTIVITY_INFO, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                modemProxy.getModemActivityInfo(rr.mSerial);
-                Message msg =
-                        mRilHandler.obtainMessage(EVENT_BLOCKING_RESPONSE_TIMEOUT, rr.mSerial);
-                mRilHandler.sendMessageDelayed(msg, DEFAULT_BLOCKING_MESSAGE_RESPONSE_TIMEOUT_MS);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getModemActivityInfo", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("getModemActivityInfo", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_ACTIVITY_INFO, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "getModemActivityInfo", () -> {
+            modemProxy.getModemActivityInfo(rr.mSerial);
+            Message msg = mRilHandler.obtainMessage(EVENT_BLOCKING_RESPONSE_TIMEOUT, rr.mSerial);
+            mRilHandler.sendMessageDelayed(msg, DEFAULT_BLOCKING_MESSAGE_RESPONSE_TIMEOUT_MS);
+        });
     }
 
     @Override
@@ -4586,266 +4215,208 @@
             Message result, WorkSource workSource) {
         Objects.requireNonNull(carrierRestrictionRules, "Carrier restriction cannot be null.");
 
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_ALLOWED_CARRIERS, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " params: " + carrierRestrictionRules);
-            }
-
-            try {
-                simProxy.setAllowedCarriers(rr.mSerial, carrierRestrictionRules, result);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setAllowedCarriers", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("setAllowedCarriers", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_ALLOWED_CARRIERS, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " params: " + carrierRestrictionRules);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "setAllowedCarriers", () -> {
+            simProxy.setAllowedCarriers(rr.mSerial, carrierRestrictionRules);
+        });
     }
 
     @Override
     public void getAllowedCarriers(Message result, WorkSource workSource) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_ALLOWED_CARRIERS, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.getAllowedCarriers(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getAllowedCarriers", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("getAllowedCarriers", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_ALLOWED_CARRIERS, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "getAllowedCarriers", () -> {
+            simProxy.getAllowedCarriers(rr.mSerial);
+        });
     }
 
     @Override
     public void sendDeviceState(int stateType, boolean state, Message result) {
-        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
-        if (!modemProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SEND_DEVICE_STATE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + " "
-                        + stateType + ":" + state);
-            }
-
-            try {
-                modemProxy.sendDeviceState(rr.mSerial, stateType, state);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "sendDeviceState", e);
-            }
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
+        if (!canMakeRequest("sendDeviceState", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SEND_DEVICE_STATE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + " "
+                    + stateType + ":" + state);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_MODEM, rr, "sendDeviceState", () -> {
+            modemProxy.sendDeviceState(rr.mSerial, stateType, state);
+        });
     }
 
     @Override
     public void setUnsolResponseFilter(int filter, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (!networkProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " " + filter);
-            }
-
-            try {
-                networkProxy.setIndicationFilter(rr.mSerial, filter);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setIndicationFilter", e);
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setUnsolResponseFilter", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " " + filter);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setUnsolResponseFilter", () -> {
+            networkProxy.setIndicationFilter(rr.mSerial, filter);
+        });
     }
 
     @Override
     public void setSignalStrengthReportingCriteria(
             @NonNull List<SignalThresholdInfo> signalThresholdInfos, @Nullable Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
-                    result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.setSignalStrengthReportingCriteria(rr.mSerial, signalThresholdInfos);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "setSignalStrengthReportingCriteria", e);
-            }
-        } else {
-            riljLoge("setSignalStrengthReportingCriteria ignored on IRadio version less than 1.2");
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setSignalStrengthReportingCriteria", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setSignalStrengthReportingCriteria",
+                () -> {
+                    networkProxy.setSignalStrengthReportingCriteria(rr.mSerial,
+                            signalThresholdInfos);
+                });
     }
 
     @Override
     public void setLinkCapacityReportingCriteria(int hysteresisMs, int hysteresisDlKbps,
             int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran,
             Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.setLinkCapacityReportingCriteria(rr.mSerial, hysteresisMs,
-                        hysteresisDlKbps, hysteresisUlKbps, thresholdsDlKbps, thresholdsUlKbps,
-                        ran);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(
-                        HAL_SERVICE_NETWORK, "setLinkCapacityReportingCriteria", e);
-            }
-        } else {
-            riljLoge("setLinkCapacityReportingCriteria ignored on IRadio version less than 1.2");
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setLinkCapacityReportingCriteria", networkProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setLinkCapacityReportingCriteria",
+                () -> {
+                    networkProxy.setLinkCapacityReportingCriteria(rr.mSerial, hysteresisMs,
+                            hysteresisDlKbps, hysteresisUlKbps, thresholdsDlKbps, thresholdsUlKbps,
+                            ran);
+                });
     }
 
     @Override
     public void setSimCardPower(int state, Message result, WorkSource workSource) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (!simProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIM_CARD_POWER, result,
-                    getDefaultWorkSourceIfInvalid(workSource));
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " " + state);
-            }
-
-            try {
-                simProxy.setSimCardPower(rr.mSerial, state, result);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setSimCardPower", e);
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("setSimCardPower", simProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIM_CARD_POWER, result,
+                getDefaultWorkSourceIfInvalid(workSource));
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " " + state);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "setSimCardPower", () -> {
+            simProxy.setSimCardPower(rr.mSerial, state);
+        });
     }
 
     @Override
     public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
             Message result) {
         Objects.requireNonNull(imsiEncryptionInfo, "ImsiEncryptionInfo cannot be null.");
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (simProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION, result,
-                    mRILDefaultWorkSource);
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.setCarrierInfoForImsiEncryption(rr.mSerial, imsiEncryptionInfo);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM,
-                        "setCarrierInfoForImsiEncryption", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "setCarrierInfoForImsiEncryption: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("setCarrierInfoForImsiEncryption", simProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION, result,
+                mRILDefaultWorkSource);
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "setCarrierInfoForImsiEncryption", () -> {
+            simProxy.setCarrierInfoForImsiEncryption(rr.mSerial, imsiEncryptionInfo);
+        });
     }
 
     @Override
     public void startNattKeepalive(int contextId, KeepalivePacketData packetData,
             int intervalMillis, Message result) {
         Objects.requireNonNull(packetData, "KeepaliveRequest cannot be null.");
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (dataProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_START_KEEPALIVE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                dataProxy.startKeepalive(rr.mSerial, contextId, packetData, intervalMillis, result);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "startNattKeepalive", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startNattKeepalive: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("startNattKeepalive", dataProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_START_KEEPALIVE, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "startNattKeepalive", () -> {
+            dataProxy.startKeepalive(rr.mSerial, contextId, packetData, intervalMillis, result);
+        });
     }
 
     @Override
     public void stopNattKeepalive(int sessionHandle, Message result) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (dataProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_STOP_KEEPALIVE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                dataProxy.stopKeepalive(rr.mSerial, sessionHandle);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "stopNattKeepalive", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "stopNattKeepalive: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("stopNattKeepalive", dataProxy, result, RADIO_HAL_VERSION_1_4)) {
+            return;
         }
-    }
 
-    @Override
-    public void getIMEI(Message result) {
-        throw new RuntimeException("getIMEI not expected to be called");
-    }
+        RILRequest rr = obtainRequest(RIL_REQUEST_STOP_KEEPALIVE, result, mRILDefaultWorkSource);
 
-    @Override
-    public void getIMEISV(Message result) {
-        throw new RuntimeException("getIMEISV not expected to be called");
-    }
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
 
-    /**
-     * @deprecated
-     */
-    @Deprecated
-    @Override
-    public void getLastPdpFailCause(Message result) {
-        throw new RuntimeException("getLastPdpFailCause not expected to be called");
-    }
-
-    /**
-     * The preferred new alternative to getLastPdpFailCause
-     */
-    @Override
-    public void getLastDataCallFailCause(Message result) {
-        throw new RuntimeException("getLastDataCallFailCause not expected to be called");
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "stopNattKeepalive", () -> {
+            dataProxy.stopKeepalive(rr.mSerial, sessionHandle);
+        });
     }
 
     /**
@@ -4856,30 +4427,22 @@
      */
     @Override
     public void enableUiccApplications(boolean enable, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (simProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_UICC_APPLICATIONS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " " + enable);
-            }
-
-            try {
-                simProxy.enableUiccApplications(rr.mSerial, enable);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "enableUiccApplications", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "enableUiccApplications: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("enableUiccApplications", simProxy, result, RADIO_HAL_VERSION_1_5)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_UICC_APPLICATIONS, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " " + enable);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "enableUiccApplications", () -> {
+            simProxy.enableUiccApplications(rr.mSerial, enable);
+        });
     }
 
     /**
@@ -4889,31 +4452,22 @@
      */
     @Override
     public void areUiccApplicationsEnabled(Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (simProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.areUiccApplicationsEnabled(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "areUiccApplicationsEnabled", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "areUiccApplicationsEnabled: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("areUiccApplicationsEnabled", simProxy, result,
+                RADIO_HAL_VERSION_1_5)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "areUiccApplicationsEnabled", () -> {
+            simProxy.areUiccApplicationsEnabled(rr.mSerial);
+        });
     }
 
     /**
@@ -4921,13 +4475,8 @@
      */
     @Override
     public boolean canToggleUiccApplicationsEnablement() {
-        return !getRadioServiceProxy(RadioSimProxy.class, null).isEmpty()
-                && mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5);
-    }
-
-    @Override
-    public void resetRadio(Message result) {
-        throw new RuntimeException("resetRadio not expected to be called");
+        return canMakeRequest("canToggleUiccApplicationsEnablement",
+                getRadioServiceProxy(RadioSimProxy.class), null, RADIO_HAL_VERSION_1_5);
     }
 
     /**
@@ -4935,22 +4484,22 @@
      */
     @Override
     public void handleCallSetupRequestFromSim(boolean accept, Message result) {
-        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
-        if (!voiceProxy.isEmpty()) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM,
-                    result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                voiceProxy.handleStkCallSetupRequestFromSim(rr.mSerial, accept);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(
-                        HAL_SERVICE_VOICE, "handleStkCallSetupRequestFromSim", e);
-            }
+        RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
+        if (!canMakeRequest("handleCallSetupRequestFromSim", voiceProxy, result,
+                RADIO_HAL_VERSION_1_4)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM,
+                result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "handleCallSetupRequestFromSim", () -> {
+            voiceProxy.handleStkCallSetupRequestFromSim(rr.mSerial, accept);
+        });
     }
 
     /**
@@ -4958,29 +4507,20 @@
      */
     @Override
     public void getBarringInfo(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_BARRING_INFO, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getBarringInfo(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getBarringInfo", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "getBarringInfo: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getBarringInfo", networkProxy, result, RADIO_HAL_VERSION_1_5)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_BARRING_INFO, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getBarringInfo", () -> {
+            networkProxy.getBarringInfo(rr.mSerial);
+        });
     }
 
     /**
@@ -4988,25 +4528,20 @@
      */
     @Override
     public void allocatePduSessionId(Message result) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (dataProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_ALLOCATE_PDU_SESSION_ID, result,
-                    mRILDefaultWorkSource);
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                dataProxy.allocatePduSessionId(rr.mSerial);
-            } catch (RemoteException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "allocatePduSessionId", e);
-            }
-        } else {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("allocatePduSessionId", dataProxy, result, RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ALLOCATE_PDU_SESSION_ID, result,
+                mRILDefaultWorkSource);
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "allocatePduSessionId", () -> {
+            dataProxy.allocatePduSessionId(rr.mSerial);
+        });
     }
 
     /**
@@ -5014,25 +4549,20 @@
      */
     @Override
     public void releasePduSessionId(Message result, int pduSessionId) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (dataProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_RELEASE_PDU_SESSION_ID, result,
-                    mRILDefaultWorkSource);
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                dataProxy.releasePduSessionId(rr.mSerial, pduSessionId);
-            } catch (RemoteException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "releasePduSessionId", e);
-            }
-        } else {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("releasePduSessionId", dataProxy, result, RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_RELEASE_PDU_SESSION_ID, result,
+                mRILDefaultWorkSource);
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "releasePduSessionId", () -> {
+            dataProxy.releasePduSessionId(rr.mSerial, pduSessionId);
+        });
     }
 
     /**
@@ -5040,28 +4570,19 @@
      */
     @Override
     public void startHandover(Message result, int callId) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (dataProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_START_HANDOVER, result,
-                    mRILDefaultWorkSource);
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                dataProxy.startHandover(rr.mSerial, callId);
-            } catch (RemoteException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "startHandover", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startHandover: REQUEST_NOT_SUPPORTED");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("startHandover", dataProxy, result, RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_START_HANDOVER, result, mRILDefaultWorkSource);
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "startHandover", () -> {
+            dataProxy.startHandover(rr.mSerial, callId);
+        });
     }
 
     /**
@@ -5069,26 +4590,19 @@
      */
     @Override
     public void cancelHandover(Message result, int callId) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (dataProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_HANDOVER, result,
-                    mRILDefaultWorkSource);
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                dataProxy.cancelHandover(rr.mSerial, callId);
-            } catch (RemoteException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "cancelHandover", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "cancelHandover: REQUEST_NOT_SUPPORTED");
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("cancelHandover", dataProxy, result, RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_HANDOVER, result, mRILDefaultWorkSource);
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "cancelHandover", () -> {
+            dataProxy.cancelHandover(rr.mSerial, callId);
+        });
     }
 
     /**
@@ -5096,115 +4610,79 @@
      */
     @Override
     public void getSlicingConfig(Message result) {
-        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
-        if (dataProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLICING_CONFIG, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                dataProxy.getSlicingConfig(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "getSlicingConfig", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "getSlicingConfig: REQUEST_NOT_SUPPORTED");
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
+        RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class);
+        if (!canMakeRequest("getSlicingConfig", dataProxy, result, RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLICING_CONFIG, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_DATA, rr, "getSlicingConfig", () -> {
+            dataProxy.getSlicingConfig(rr.mSerial);
+        });
     }
 
     @Override
     public void getSimPhonebookRecords(Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (simProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.getSimPhonebookRecords(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getSimPhonebookRecords", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "getSimPhonebookRecords: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("getSimPhonebookRecords", simProxy, result, RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "getSimPhonebookRecords", () -> {
+            simProxy.getSimPhonebookRecords(rr.mSerial);
+        });
     }
 
     @Override
     public void getSimPhonebookCapacity(Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (simProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                simProxy.getSimPhonebookCapacity(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getSimPhonebookCapacity", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "getSimPhonebookCapacity: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("getSimPhonebookCapacity", simProxy, result, RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "getSimPhonebookCapacity", () -> {
+            simProxy.getSimPhonebookCapacity(rr.mSerial);
+        });
     }
 
     @Override
     public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecord, Message result) {
-        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
-        if (simProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " with " + phonebookRecord.toString());
-            }
-
-            try {
-                simProxy.updateSimPhonebookRecords(rr.mSerial, phonebookRecord);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "updateSimPhonebookRecords", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "updateSimPhonebookRecords: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
+        if (!canMakeRequest("updateSimPhonebookRecord", simProxy, result, RADIO_HAL_VERSION_1_6)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " with " + phonebookRecord.toString());
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "updateSimPhonebookRecord", () -> {
+            simProxy.updateSimPhonebookRecords(rr.mSerial, phonebookRecord);
+        });
     }
 
     /**
@@ -5216,31 +4694,20 @@
     @Override
     public void setUsageSetting(Message result,
             /* @TelephonyManager.UsageSetting */ int usageSetting) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_USAGE_SETTING, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.setUsageSetting(rr.mSerial, usageSetting);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setUsageSetting", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "setUsageSetting: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setUsageSetting", networkProxy, result, RADIO_HAL_VERSION_2_0)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_USAGE_SETTING, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setUsageSetting", () -> {
+            networkProxy.setUsageSetting(rr.mSerial, usageSetting);
+        });
     }
 
     /**
@@ -5250,62 +4717,40 @@
      */
     @Override
     public void getUsageSetting(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_USAGE_SETTING, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.getUsageSetting(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getUsageSetting", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "getUsageSetting: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("getUsageSetting", networkProxy, result, RADIO_HAL_VERSION_2_0)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_USAGE_SETTING, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "getUsageSetting", () -> {
+            networkProxy.getUsageSetting(rr.mSerial);
+        });
     }
 
     @Override
     public void setSrvccCallInfo(SrvccConnection[] srvccConnections, Message result) {
-        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
-        if (imsProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_SRVCC_CALL_INFO, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                // Do not log function arg for privacy
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                imsProxy.setSrvccCallInfo(rr.mSerial,
-                        RILUtils.convertToHalSrvccCall(srvccConnections));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "setSrvccCallInfo", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "setSrvccCallInfo: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class);
+        if (!canMakeRequest("setSrvccCallInfo", imsProxy, result, RADIO_HAL_VERSION_2_0)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SRVCC_CALL_INFO, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            // Do not log function arg for privacy
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_IMS, rr, "setSrvccCallInfo", () -> {
+            imsProxy.setSrvccCallInfo(rr.mSerial, RILUtils.convertToHalSrvccCall(srvccConnections));
+        });
     }
 
     @Override
@@ -5314,164 +4759,109 @@
             @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech,
             @RegistrationManager.SuggestedAction int suggestedAction,
             int capabilities, Message result) {
-        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
-        if (imsProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " state=" + state + ", radioTech=" + imsRadioTech
-                        + ", suggested=" + suggestedAction + ", cap=" + capabilities);
-            }
-
-            android.hardware.radio.ims.ImsRegistration registrationInfo =
-                    new android.hardware.radio.ims.ImsRegistration();
-            registrationInfo.regState = RILUtils.convertImsRegistrationState(state);
-            registrationInfo.accessNetworkType = RILUtils.convertImsRegistrationTech(imsRadioTech);
-            registrationInfo.suggestedAction = suggestedAction;
-            registrationInfo.capabilities = RILUtils.convertImsCapability(capabilities);
-
-            try {
-                imsProxy.updateImsRegistrationInfo(rr.mSerial, registrationInfo);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "updateImsRegistrationInfo", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "updateImsRegistrationInfo: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class);
+        if (!canMakeRequest("updateImsRegistrationInfo", imsProxy, result, RADIO_HAL_VERSION_2_0)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " state=" + state + ", radioTech=" + imsRadioTech
+                    + ", suggested=" + suggestedAction + ", cap=" + capabilities);
+        }
+
+        android.hardware.radio.ims.ImsRegistration registrationInfo =
+                new android.hardware.radio.ims.ImsRegistration();
+        registrationInfo.regState = RILUtils.convertImsRegistrationState(state);
+        registrationInfo.accessNetworkType = RILUtils.convertImsRegistrationTech(imsRadioTech);
+        registrationInfo.suggestedAction = suggestedAction;
+        registrationInfo.capabilities = RILUtils.convertImsCapability(capabilities);
+
+        radioServiceInvokeHelper(HAL_SERVICE_IMS, rr, "updateImsRegistrationInfo", () -> {
+            imsProxy.updateImsRegistrationInfo(rr.mSerial, registrationInfo);
+        });
     }
 
     @Override
-    public void startImsTraffic(int token,
-            int trafficType, int accessNetworkType, int trafficDirection, Message result) {
-        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
-        if (imsProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_START_IMS_TRAFFIC, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + "{" + token + ", " + trafficType + ", "
-                        + accessNetworkType + ", " + trafficDirection + "}");
-            }
-
-            try {
-                imsProxy.startImsTraffic(rr.mSerial, token,
-                        RILUtils.convertImsTrafficType(trafficType), accessNetworkType,
-                        RILUtils.convertImsTrafficDirection(trafficDirection));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "startImsTraffic", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "startImsTraffic: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+    public void startImsTraffic(int token, int trafficType, int accessNetworkType,
+            int trafficDirection, Message result) {
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class);
+        if (!canMakeRequest("startImsTraffic", imsProxy, result, RADIO_HAL_VERSION_2_0)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_START_IMS_TRAFFIC, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + "{" + token + ", " + trafficType + ", "
+                    + accessNetworkType + ", " + trafficDirection + "}");
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_IMS, rr, "startImsTraffic", () -> {
+            imsProxy.startImsTraffic(rr.mSerial, token, RILUtils.convertImsTrafficType(trafficType),
+                    accessNetworkType, RILUtils.convertImsTrafficDirection(trafficDirection));
+        });
     }
 
     @Override
     public void stopImsTraffic(int token, Message result) {
-        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
-        if (imsProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_STOP_IMS_TRAFFIC, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + "{" + token + "}");
-            }
-
-            try {
-                imsProxy.stopImsTraffic(rr.mSerial, token);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "stopImsTraffic", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "stopImsTraffic: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class);
+        if (!canMakeRequest("stopImsTraffic", imsProxy, result, RADIO_HAL_VERSION_2_0)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_STOP_IMS_TRAFFIC, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + "{" + token + "}");
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_IMS, rr, "stopImsTraffic", () -> {
+            imsProxy.stopImsTraffic(rr.mSerial, token);
+        });
     }
 
     @Override
     public void triggerEpsFallback(int reason, Message result) {
-        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
-        if (imsProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_TRIGGER_EPS_FALLBACK, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " reason=" + reason);
-            }
-
-            try {
-                imsProxy.triggerEpsFallback(rr.mSerial, reason);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "triggerEpsFallback", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "triggerEpsFallback: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class);
+        if (!canMakeRequest("triggerEpsFallback", imsProxy, result, RADIO_HAL_VERSION_2_0)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_TRIGGER_EPS_FALLBACK, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " reason=" + reason);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_IMS, rr, "triggerEpsFallback", () -> {
+            imsProxy.triggerEpsFallback(rr.mSerial, reason);
+        });
     }
 
     @Override
-    public void sendAnbrQuery(int mediaType, int direction, int bitsPerSecond,
-            Message result) {
-        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
-        if (imsProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SEND_ANBR_QUERY, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                imsProxy.sendAnbrQuery(rr.mSerial, mediaType, direction, bitsPerSecond);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "sendAnbrQuery", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "sendAnbrQuery: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+    public void sendAnbrQuery(int mediaType, int direction, int bitsPerSecond, Message result) {
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class);
+        if (!canMakeRequest("sendAnbrQuery", imsProxy, result, RADIO_HAL_VERSION_2_0)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SEND_ANBR_QUERY, result, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_IMS, rr, "sendAnbrQuery", () -> {
+            imsProxy.sendAnbrQuery(rr.mSerial, mediaType, direction, bitsPerSecond);
+        });
     }
 
     /**
@@ -5479,32 +4869,22 @@
      */
     @Override
     public void setEmergencyMode(int emcMode, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_EMERGENCY_MODE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " mode=" + EmergencyConstants.emergencyModeToString(emcMode));
-            }
-
-            try {
-                networkProxy.setEmergencyMode(rr.mSerial, emcMode);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setEmergencyMode", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "setEmergencyMode: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setEmergencyMode", networkProxy, result, RADIO_HAL_VERSION_2_1)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_EMERGENCY_MODE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " mode=" + EmergencyConstants.emergencyModeToString(emcMode));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setEmergencyMode", () -> {
+            networkProxy.setEmergencyMode(rr.mSerial, emcMode);
+        });
     }
 
     /**
@@ -5514,35 +4894,25 @@
     public void triggerEmergencyNetworkScan(
             @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetwork,
             @DomainSelectionService.EmergencyScanType int scanType, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " networkType=" + RILUtils.accessNetworkTypesToString(accessNetwork)
-                        + ", scanType=" + RILUtils.scanTypeToString(scanType));
-            }
-
-            try {
-                networkProxy.triggerEmergencyNetworkScan(rr.mSerial,
-                        RILUtils.convertEmergencyNetworkScanTrigger(accessNetwork, scanType));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "triggerEmergencyNetworkScan", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "triggerEmergencyNetworkScan: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("triggerEmergencyNetworkScan", networkProxy, result,
+                RADIO_HAL_VERSION_2_1)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " networkType=" + RILUtils.accessNetworkTypesToString(accessNetwork)
+                    + ", scanType=" + RILUtils.scanTypeToString(scanType));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "triggerEmergencyNetworkScan", () -> {
+            networkProxy.triggerEmergencyNetworkScan(rr.mSerial,
+                    RILUtils.convertEmergencyNetworkScanTrigger(accessNetwork, scanType));
+        });
     }
 
     /**
@@ -5550,33 +4920,23 @@
      */
     @Override
     public void cancelEmergencyNetworkScan(boolean resetScan, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " resetScan=" + resetScan);
-            }
-
-            try {
-                networkProxy.cancelEmergencyNetworkScan(rr.mSerial, resetScan);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
-                        "cancelEmergencyNetworkScan", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "cancelEmergencyNetworkScan: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("cancelEmergencyNetworkScan", networkProxy, result,
+                RADIO_HAL_VERSION_2_1)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " resetScan=" + resetScan);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "cancelEmergencyNetworkScan", () -> {
+            networkProxy.cancelEmergencyNetworkScan(rr.mSerial, resetScan);
+        });
     }
 
     /**
@@ -5584,31 +4944,21 @@
      */
     @Override
     public void exitEmergencyMode(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_EXIT_EMERGENCY_MODE, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.exitEmergencyMode(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "exitEmergencyMode", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "exitEmergencyMode: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("exitEmergencyMode", networkProxy, result, RADIO_HAL_VERSION_2_1)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_EXIT_EMERGENCY_MODE, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "exitEmergencyMode", () -> {
+            networkProxy.exitEmergencyMode(rr.mSerial);
+        });
     }
 
     /**
@@ -5619,32 +4969,23 @@
      */
     @Override
     public void setNullCipherAndIntegrityEnabled(boolean enabled, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.setNullCipherAndIntegrityEnabled(rr.mSerial, enabled);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(
-                        HAL_SERVICE_NETWORK, "setNullCipherAndIntegrityEnabled", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "setNullCipherAndIntegrityEnabled: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setNullCipherAndIntegrityEnabled", networkProxy, result,
+                RADIO_HAL_VERSION_2_1)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setNullCipherAndIntegrityEnabled",
+                () -> {
+                    networkProxy.setNullCipherAndIntegrityEnabled(rr.mSerial, enabled);
+                });
     }
 
     /**
@@ -5654,32 +4995,22 @@
      */
     @Override
     public void isNullCipherAndIntegrityEnabled(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.isNullCipherAndIntegrityEnabled(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(
-                        HAL_SERVICE_NETWORK, "isNullCipherAndIntegrityEnabled", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "isNullCipherAndIntegrityEnabled: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("isNullCipherAndIntegrityEnabled", networkProxy, result,
+                RADIO_HAL_VERSION_2_1)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "isNullCipherAndIntegrityEnabled", () -> {
+            networkProxy.isNullCipherAndIntegrityEnabled(rr.mSerial);
+        });
     }
 
     /**
@@ -5687,31 +5018,21 @@
      */
     @Override
     public void updateImsCallStatus(@NonNull List<ImsCallInfo> imsCallInfo, Message result) {
-        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
-        if (imsProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_IMS_CALL_STATUS, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " " + imsCallInfo);
-            }
-            try {
-                imsProxy.updateImsCallStatus(rr.mSerial, RILUtils.convertImsCallInfo(imsCallInfo));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "updateImsCallStatus", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "updateImsCallStatus: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class);
+        if (!canMakeRequest("updateImsCallStatus", imsProxy, result, RADIO_HAL_VERSION_2_0)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_IMS_CALL_STATUS, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " " + imsCallInfo);
+        }
+        radioServiceInvokeHelper(HAL_SERVICE_IMS, rr, "updateImsCallStatus", () -> {
+            imsProxy.updateImsCallStatus(rr.mSerial, RILUtils.convertImsCallInfo(imsCallInfo));
+        });
     }
 
     /**
@@ -5719,32 +5040,22 @@
      */
     @Override
     public void setN1ModeEnabled(boolean enable, Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_N1_MODE_ENABLED, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " enable=" + enable);
-            }
-
-            try {
-                networkProxy.setN1ModeEnabled(rr.mSerial, enable);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setN1ModeEnabled", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "setN1ModeEnabled: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("setN1ModeEnabled", networkProxy, result, RADIO_HAL_VERSION_2_1)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_N1_MODE_ENABLED, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enable=" + enable);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setN1ModeEnabled", () -> {
+            networkProxy.setN1ModeEnabled(rr.mSerial, enable);
+        });
     }
 
     /**
@@ -5752,306 +5063,137 @@
      */
     @Override
     public void isN1ModeEnabled(Message result) {
-        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
-        if (networkProxy.isEmpty()) return;
-        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_IS_N1_MODE_ENABLED, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
-            }
-
-            try {
-                networkProxy.isN1ModeEnabled(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "isN1ModeEnabled", e);
-            }
-        } else {
-            if (RILJ_LOGD) {
-                Rlog.d(RILJ_LOG_TAG, "isN1ModeEnabled: REQUEST_NOT_SUPPORTED");
-            }
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest("isN1ModeEnabled", networkProxy, result, RADIO_HAL_VERSION_2_1)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_IS_N1_MODE_ENABLED, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "isN1ModeEnabled", () -> {
+            networkProxy.isN1ModeEnabled(rr.mSerial);
+        });
     }
 
     /**
-     * Get feature capabilities supported by satellite.
-     *
-     * @param result Message that will be sent back to the requester
+     * {@inheritDoc}
      */
     @Override
-    public void getSatelliteCapabilities(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
+    public void setCellularIdentifierTransparencyEnabled(boolean enable, Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest(
+                "setCellularIdentifierTransparencyEnabled",
+                networkProxy,
+                result,
+                RADIO_HAL_VERSION_2_2)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enable=" + enable);
+        }
+
+        radioServiceInvokeHelper(
+                HAL_SERVICE_NETWORK,
+                rr,
+                "setCellularIdentifierTransparencyEnabled",
+                () -> {
+                    networkProxy.setCellularIdentifierTransparencyEnabled(rr.mSerial, enable);
+                });
     }
 
     /**
-     * Turn satellite modem on/off.
-     *
-     * @param result Message that will be sent back to the requester
-     * @param on True for turning on.
-     *           False for turning off.
+     * {@inheritDoc}
      */
     @Override
-    public void setSatellitePower(Message result, boolean on) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
+    public void isCellularIdentifierTransparencyEnabled(Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest(
+                "isCellularIdentifierTransparencyEnabled",
+                networkProxy,
+                result,
+                RADIO_HAL_VERSION_2_2)) {
+            return;
         }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_IS_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        radioServiceInvokeHelper(
+                HAL_SERVICE_NETWORK,
+                rr,
+                "isCellularIdentifierTransparencyEnabled",
+                () -> {
+                    networkProxy.isCellularIdentifierTransparencyEnabled(rr.mSerial);
+                });
+    }
+
+   /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setSecurityAlgorithmsUpdatedEnabled(boolean enable, Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest(
+                "setSecurityAlgorithmsUpdatedEnabled",
+                networkProxy,
+                result,
+                RADIO_HAL_VERSION_2_2)) {
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " enable=" + enable);
+        }
+
+        radioServiceInvokeHelper(HAL_SERVICE_NETWORK, rr, "setSecurityAlgorithmsUpdatedEnabled",
+                () -> {
+                    networkProxy.setSecurityAlgorithmsUpdatedEnabled(rr.mSerial, enable);
+            });
     }
 
     /**
-     * Get satellite modem state.
-     *
-     * @param result Message that will be sent back to the requester
+     * {@inheritDoc}
      */
     @Override
-    public void getSatellitePowerState(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
+    public void isSecurityAlgorithmsUpdatedEnabled(Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest(
+                "isSecurityAlgorithmsUpdatedEnabled",
+                networkProxy,
+                result,
+                RADIO_HAL_VERSION_2_2)) {
+            return;
         }
-    }
 
-    /**
-     * Get satellite provision state.
-     *
-     * @param result Message that will be sent back to the requester
-     */
-    @Override
-    public void getSatelliteProvisionState(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
+        RILRequest rr = obtainRequest(RIL_REQUEST_IS_SECURITY_ALGORITHMS_UPDATED_ENABLED, result,
+                mRILDefaultWorkSource);
 
-    /**
-     * Provision the subscription with a satellite provider. This is needed to register the
-     * subscription if the provider allows dynamic registration.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param imei IMEI of the SIM associated with the satellite modem.
-     * @param msisdn MSISDN of the SIM associated with the satellite modem.
-     * @param imsi IMSI of the SIM associated with the satellite modem.
-     * @param features List of features to be provisioned.
-     */
-    @Override
-    public void provisionSatelliteService(
-            Message result, String imei, String msisdn, String imsi, int[] features) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
         }
-    }
 
-    /**
-     * Add contacts that are allowed to be used for satellite communication. This is applicable for
-     * incoming messages as well.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param contacts List of allowed contacts to be added.
-     */
-    @Override
-    public void addAllowedSatelliteContacts(Message result, String[] contacts) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Remove contacts that are allowed to be used for satellite communication. This is applicable
-     * for incoming messages as well.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param contacts List of allowed contacts to be removed.
-     */
-    @Override
-    public void removeAllowedSatelliteContacts(Message result, String[] contacts) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Send text messages.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param messages List of messages in text format to be sent.
-     * @param destination The recipient of the message.
-     * @param latitude The current latitude of the device.
-     * @param longitude The current longitude of the device.
-     */
-    @Override
-    public void sendSatelliteMessages(Message result, String[] messages, String destination,
-            double latitude, double longitude) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Get pending messages.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    @Override
-    public void getPendingSatelliteMessages(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Get current satellite registration mode.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    @Override
-    public void getSatelliteMode(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Set the filter for what type of indication framework want to receive from modem.
-     *
-     * @param result Message that will be sent back to the requester.
-     * @param filterBitmask The filter bitmask identifying what type of indication framework want to
-     *                         receive from modem.
-     */
-    @Override
-    public void setSatelliteIndicationFilter(Message result, int filterBitmask) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Check whether satellite modem is supported by the device.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    @Override
-    public void isSatelliteSupported(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * User started pointing to the satellite. Modem should continue to update the ponting input
-     * as user moves device.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    @Override
-    public void startSendingSatellitePointingInfo(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Stop pointing to satellite indications.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    @Override
-    public void stopSendingSatellitePointingInfo(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Get max text limit for messaging per message.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    @Override
-    public void getMaxCharactersPerSatelliteTextMessage(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Get whether satellite communication is allowed for the current location
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    @Override
-    public void isSatelliteCommunicationAllowedForCurrentLocation(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
-    /**
-     * Get time for next visibility of satellite.
-     *
-     * @param result Message that will be sent back to the requester.
-     */
-    @Override
-    public void getTimeForNextSatelliteVisibility(Message result) {
-        // Satellite HAL APIs are not supported before Android V.
-        if (result != null) {
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
+        radioServiceInvokeHelper(
+                HAL_SERVICE_NETWORK, rr, "isSecurityAlgorithmsUpdatedEnabled", () -> {
+                networkProxy.isSecurityAlgorithmsUpdatedEnabled(rr.mSerial);
+            });
     }
 
     //***** Private Methods
@@ -6076,8 +5218,7 @@
             rr = mRequestList.get(serial);
         }
         if (rr == null) {
-            Rlog.w(RILJ_LOG_TAG, "processRequestAck: Unexpected solicited ack response! "
-                    + "serial: " + serial);
+            riljLogw("processRequestAck: Unexpected solicited ack response! serial: " + serial);
         } else {
             decrementWakeLock(rr);
             if (RILJ_LOGD) {
@@ -6135,7 +5276,7 @@
                 rr = mRequestList.get(serial);
             }
             if (rr == null) {
-                Rlog.w(RILJ_LOG_TAG, "Unexpected solicited ack response! sn: " + serial);
+                riljLogw("Unexpected solicited ack response! sn: " + serial);
             } else {
                 decrementWakeLock(rr);
                 if (mRadioBugDetector != null) {
@@ -6151,8 +5292,8 @@
 
         rr = findAndRemoveRequestFromList(serial);
         if (rr == null) {
-            Rlog.e(RILJ_LOG_TAG, "processResponse: Unexpected response! serial: " + serial
-                    + " ,error: " + error);
+            riljLoge("processResponse: Unexpected response! serial: " + serial
+                    + ", error: " + error);
             return null;
         }
         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial);
@@ -6213,12 +5354,12 @@
         } else {
             switch (rr.mRequest) {
                 case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND:
-                if (mTestingEmergencyCall.getAndSet(false)) {
-                    if (mEmergencyCallbackModeRegistrant != null) {
-                        riljLog("testing emergency call, notify ECM Registrants");
-                        mEmergencyCallbackModeRegistrant.notifyRegistrant();
+                    if (mTestingEmergencyCall.getAndSet(false)) {
+                        if (mEmergencyCallbackModeRegistrant != null) {
+                            riljLog("testing emergency call, notify ECM Registrants");
+                            mEmergencyCallbackModeRegistrant.notifyRegistrant();
+                        }
                     }
-                }
             }
         }
         return rr;
@@ -6232,7 +5373,7 @@
      * @param responseInfo RadioResponseInfo received in the callback
      * @param ret object to be returned to request sender
      */
-    @VisibleForTesting
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
     public void processResponseDone(RILRequest rr, RadioResponseInfo responseInfo, Object ret) {
         processResponseDoneInternal(rr, responseInfo.error, responseInfo.type, ret);
     }
@@ -6270,14 +5411,22 @@
     private void processResponseDoneInternal(RILRequest rr, int rilError, int responseType,
             Object ret) {
         if (rilError == 0) {
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "< " + RILUtils.requestToString(rr.mRequest)
-                        + " " + retToString(rr.mRequest, ret));
+            if (isLogOrTrace()) {
+                String logStr = rr.serialString() + "< " + RILUtils.requestToString(rr.mRequest)
+                        + " " + retToString(rr.mRequest, ret);
+                if (RILJ_LOGD) {
+                    riljLog(logStr);
+                }
+                Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
             }
         } else {
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "< " + RILUtils.requestToString(rr.mRequest)
-                        + " error " + rilError);
+            if (isLogOrTrace()) {
+                String logStr = rr.serialString() + "< " + RILUtils.requestToString(rr.mRequest)
+                        + " error " + rilError;
+                if (RILJ_LOGD) {
+                    riljLog(logStr);
+                }
+                Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
             }
             rr.onError(rilError, ret);
         }
@@ -6321,7 +5470,7 @@
                 mRILDefaultWorkSource);
         acquireWakeLock(rr, FOR_ACK_WAKELOCK);
         if (service == HAL_SERVICE_RADIO) {
-            IRadio radioProxy = getRadioProxy(null);
+            IRadio radioProxy = getRadioProxy();
             if (radioProxy != null) {
                 try {
                     radioProxy.responseAcknowledgement();
@@ -6330,10 +5479,10 @@
                     riljLoge("sendAck: " + e);
                 }
             } else {
-                Rlog.e(RILJ_LOG_TAG, "Error trying to send ack, radioProxy = null");
+                riljLoge("Error trying to send ack, radioProxy = null");
             }
         } else {
-            RadioServiceProxy serviceProxy = getRadioServiceProxy(service, null);
+            RadioServiceProxy serviceProxy = getRadioServiceProxy(service);
             if (!serviceProxy.isEmpty()) {
                 try {
                     serviceProxy.responseAcknowledgement();
@@ -6342,7 +5491,7 @@
                     riljLoge("sendAck: " + e);
                 }
             } else {
-                Rlog.e(RILJ_LOG_TAG, "Error trying to send ack, serviceProxy is empty");
+                riljLoge("Error trying to send ack, serviceProxy is empty");
             }
         }
         rr.release();
@@ -6369,11 +5518,11 @@
     private void acquireWakeLock(RILRequest rr, int wakeLockType) {
         synchronized (rr) {
             if (rr.mWakeLockType != INVALID_WAKELOCK) {
-                Rlog.d(RILJ_LOG_TAG, "Failed to aquire wakelock for " + rr.serialString());
+                riljLog("Failed to acquire wakelock for " + rr.serialString());
                 return;
             }
 
-            switch(wakeLockType) {
+            switch (wakeLockType) {
                 case FOR_WAKELOCK:
                     synchronized (mWakeLock) {
                         mWakeLock.acquire();
@@ -6405,7 +5554,7 @@
                     }
                     break;
                 default: //WTF
-                    Rlog.w(RILJ_LOG_TAG, "Acquiring Invalid Wakelock type " + wakeLockType);
+                    riljLogw("Acquiring Invalid Wakelock type " + wakeLockType);
                     return;
             }
             rr.mWakeLockType = wakeLockType;
@@ -6459,7 +5608,7 @@
                 case INVALID_WAKELOCK:
                     break;
                 default:
-                    Rlog.w(RILJ_LOG_TAG, "Decrementing Invalid Wakelock type " + rr.mWakeLockType);
+                    riljLogw("Decrementing Invalid Wakelock type " + rr.mWakeLockType);
             }
             rr.mWakeLockType = INVALID_WAKELOCK;
         }
@@ -6470,8 +5619,7 @@
         if (wakeLockType == FOR_WAKELOCK) {
             synchronized (mWakeLock) {
                 if (mWakeLockCount == 0 && !mWakeLock.isHeld()) return false;
-                Rlog.d(RILJ_LOG_TAG, "NOTE: mWakeLockCount is " + mWakeLockCount
-                        + " at time of clearing");
+                riljLog("NOTE: mWakeLockCount is " + mWakeLockCount + " at time of clearing");
                 mWakeLockCount = 0;
                 mWakeLock.release();
                 mClientWakelockTracker.stopTrackingAll();
@@ -6498,15 +5646,14 @@
         synchronized (mRequestList) {
             int count = mRequestList.size();
             if (RILJ_LOGD && loggable) {
-                Rlog.d(RILJ_LOG_TAG, "clearRequestList " + " mWakeLockCount="
-                        + mWakeLockCount + " mRequestList=" + count);
+                riljLog("clearRequestList " + " mWakeLockCount=" + mWakeLockCount
+                        + " mRequestList=" + count);
             }
 
             for (int i = 0; i < count; i++) {
                 rr = mRequestList.valueAt(i);
                 if (RILJ_LOGD && loggable) {
-                    Rlog.d(RILJ_LOG_TAG, i + ": [" + rr.mSerial + "] "
-                            + RILUtils.requestToString(rr.mRequest));
+                    riljLog(i + ": [" + rr.mSerial + "] " + RILUtils.requestToString(rr.mRequest));
                 }
                 rr.onError(error, null);
                 decrementWakeLock(rr);
@@ -6571,6 +5718,7 @@
             case RIL_REQUEST_GET_IMEISV:
             case RIL_REQUEST_SIM_OPEN_CHANNEL:
             case RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL:
+            case RIL_REQUEST_DEVICE_IMEI:
 
                 if (!RILJ_LOGV) {
                     // If not versbose logging just return and don't display IMSI and IMEI, IMEISV
@@ -6763,6 +5911,13 @@
         }
     }
 
+    void notifyRegistrantsImeiMappingChanged(ImeiInfo imeiInfo) {
+        if (mImeiInfoRegistrants != null) {
+            mImeiInfoRegistrants.notifyRegistrants(
+                    new AsyncResult(null, imeiInfo, null));
+        }
+    }
+
     @UnsupportedAppUsage
     void riljLog(String msg) {
         Rlog.d(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
@@ -6776,18 +5931,22 @@
         Rlog.v(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
     }
 
+    void riljLogw(String msg) {
+        Rlog.w(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
+    }
+
     boolean isLogOrTrace() {
-        return RIL.RILJ_LOGD || Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK);
+        return RILJ_LOGD || Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK);
     }
 
     boolean isLogvOrTrace() {
-        return RIL.RILJ_LOGV || Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK);
+        return RILJ_LOGV || Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK);
     }
 
     @UnsupportedAppUsage
     void unsljLog(int response) {
         String logStr = RILUtils.responseToString(response);
-        if (RIL.RILJ_LOGD) {
+        if (RILJ_LOGD) {
             riljLog("[UNSL]< " + logStr);
         }
         Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
@@ -6796,7 +5955,7 @@
     @UnsupportedAppUsage
     void unsljLogMore(int response, String more) {
         String logStr = RILUtils.responseToString(response) + " " + more;
-        if (RIL.RILJ_LOGD) {
+        if (RILJ_LOGD) {
             riljLog("[UNSL]< " + logStr);
         }
         Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
@@ -6805,7 +5964,7 @@
     @UnsupportedAppUsage
     void unsljLogRet(int response, Object ret) {
         String logStr = RILUtils.responseToString(response) + " " + retToString(response, ret);
-        if (RIL.RILJ_LOGD) {
+        if (RILJ_LOGD) {
             riljLog("[UNSL]< " + logStr);
         }
         Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
@@ -6814,7 +5973,7 @@
     @UnsupportedAppUsage
     void unsljLogvRet(int response, Object ret) {
         String logStr = RILUtils.responseToString(response) + " " + retToString(response, ret);
-        if (RIL.RILJ_LOGV) {
+        if (RILJ_LOGV) {
             riljLogv("[UNSL]< " + logStr);
         }
         Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
@@ -6867,58 +6026,6 @@
         return mClientWakelockTracker.getClientRequestStats();
     }
 
-    /**
-     * Fixup for SignalStrength 1.0 to Assume GSM to WCDMA when
-     * The current RAT type is one of the UMTS RATs.
-     * @param signalStrength the initial signal strength
-     * @return a new SignalStrength if RAT is UMTS or existing SignalStrength
-     */
-    public SignalStrength fixupSignalStrength10(SignalStrength signalStrength) {
-        List<CellSignalStrengthGsm> gsmList = signalStrength.getCellSignalStrengths(
-                CellSignalStrengthGsm.class);
-        // If GSM is not the primary type, then bail out; no fixup needed.
-        if (gsmList.isEmpty() || !gsmList.get(0).isValid()) {
-            return signalStrength;
-        }
-
-        CellSignalStrengthGsm gsmStrength = gsmList.get(0);
-
-        // Use the voice RAT which is a guarantee in GSM and UMTS
-        int voiceRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
-        Phone phone = PhoneFactory.getPhone(mPhoneId);
-        if (phone != null) {
-            ServiceState ss = phone.getServiceState();
-            if (ss != null) {
-                voiceRat = ss.getRilVoiceRadioTechnology();
-            }
-        }
-        switch (voiceRat) {
-            case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: /* fallthrough */
-            case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: /* fallthrough */
-            case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: /* fallthrough */
-            case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: /* fallthrough */
-            case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: /* fallthrough */
-                break;
-            default:
-                // If we are not currently on WCDMA/HSPA, then we don't need to do a fixup.
-                return signalStrength;
-        }
-
-        // The service state reports WCDMA, and the SignalStrength is reported for GSM, so at this
-        // point we take an educated guess that the GSM SignalStrength report is actually for
-        // WCDMA. Also, if we are in WCDMA/GSM we can safely assume that there are no other valid
-        // signal strength reports (no SRLTE, which is the only supported case in HAL 1.0).
-        // Thus, we just construct a new SignalStrength and migrate RSSI and BER from the
-        // GSM report to the WCDMA report, leaving everything else empty.
-        return new SignalStrength(
-                new CellSignalStrengthCdma(), new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(gsmStrength.getRssi(),
-                        gsmStrength.getBitErrorRate(),
-                        CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE),
-                new CellSignalStrengthTdscdma(), new CellSignalStrengthLte(),
-                new CellSignalStrengthNr());
-    }
-
     void notifyBarringInfoChanged(@NonNull BarringInfo barringInfo) {
         mLastBarringInfo = barringInfo;
         mBarringInfoChangedRegistrants.notifyRegistrants(new AsyncResult(null, barringInfo, null));
@@ -6951,6 +6058,7 @@
         switch (interfaceVersion) {
             case 1: return RADIO_HAL_VERSION_2_0;
             case 2: return RADIO_HAL_VERSION_2_1;
+            case 3: return RADIO_HAL_VERSION_2_2;
             default: return RADIO_HAL_VERSION_UNKNOWN;
         }
     }
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index ae8d033..8897db4 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -91,12 +91,14 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_IMEI;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_IMEISV;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_IMSI;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_MODEM_STATUS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_MUTE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_NEIGHBORING_CELL_IDS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PHONE_CAPABILITY;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_RADIO_CAPABILITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIM_STATUS;
@@ -115,9 +117,11 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IMS_REGISTRATION_STATE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IMS_SEND_SMS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ISIM_AUTHENTICATION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_N1_MODE_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_SECURITY_ALGORITHMS_UPDATED_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_VONR_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_LAST_CALL_FAIL_CAUSE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE;
@@ -156,6 +160,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CALL_FORWARD;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CALL_WAITING;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CLIR;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DATA_PROFILE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DATA_THROTTLING;
@@ -164,6 +169,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_FACILITY_LOCK;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_INITIAL_ATTACH_APN;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOCATION_UPDATES;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_MUTE;
@@ -174,6 +180,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_DATA_MODEM;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_RADIO_CAPABILITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SIM_CARD_POWER;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SMSC_ADDRESS;
@@ -229,6 +236,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_PRL_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CELLULAR_IDENTIFIER_DISCLOSED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CELL_INFO_LIST;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CONNECTION_SETUP_FAILURE;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_DATA_CALL_LIST_CHANGED;
@@ -239,6 +247,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_HARDWARE_CONFIG_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ICC_SLOT_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_IMEI_MAPPING_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_KEEPALIVE_STATUS;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_LCEDATA_RECV;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_MODEM_RESTART;
@@ -269,7 +278,9 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESTRICTED_STATE_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RIL_CONNECTED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RINGBACK_TONE;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SECURITY_ALGORITHMS_UPDATED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIGNAL_STRENGTH;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIMULTANEOUS_CALLING_SUPPORT_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIM_REFRESH;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIM_SMS_STORAGE_FULL;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SLICING_CONFIG_CHANGED;
@@ -297,6 +308,7 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation;
 import android.telephony.BarringInfo;
+import android.telephony.CarrierInfo;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellConfigLte;
 import android.telephony.CellIdentity;
@@ -320,9 +332,10 @@
 import android.telephony.CellSignalStrengthNr;
 import android.telephony.CellSignalStrengthTdscdma;
 import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.CellularIdentifierDisclosure;
 import android.telephony.ClosedSubscriberGroupInfo;
 import android.telephony.DomainSelectionService;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.ModemInfo;
 import android.telephony.NetworkRegistrationInfo;
@@ -330,6 +343,7 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.RadioAccessSpecifier;
+import android.telephony.SecurityAlgorithmUpdate;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SignalThresholdInfo;
@@ -357,7 +371,6 @@
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsRegistrationImplBase.ImsDeregistrationReason;
-import android.telephony.satellite.SatelliteManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -874,41 +887,6 @@
     }
 
     /**
-     * Convert to DataProfileInfo defined in radio/1.0/types.hal
-     * @param dp Data profile
-     * @return The converted DataProfileInfo
-     */
-    public static android.hardware.radio.V1_0.DataProfileInfo convertToHalDataProfile10(
-            DataProfile dp) {
-        android.hardware.radio.V1_0.DataProfileInfo dpi =
-                new android.hardware.radio.V1_0.DataProfileInfo();
-
-        dpi.profileId = dp.getProfileId();
-        dpi.apn = dp.getApn();
-        dpi.protocol = ApnSetting.getProtocolStringFromInt(dp.getProtocolType());
-        dpi.roamingProtocol = ApnSetting.getProtocolStringFromInt(dp.getRoamingProtocolType());
-        dpi.authType = dp.getAuthType();
-        dpi.user = TextUtils.emptyIfNull(dp.getUserName());
-        dpi.password = TextUtils.emptyIfNull(dp.getPassword());
-        dpi.type = dp.getType();
-        dpi.maxConnsTime = dp.getMaxConnectionsTime();
-        dpi.maxConns = dp.getMaxConnections();
-        dpi.waitTime = dp.getWaitTime();
-        dpi.enabled = dp.isEnabled();
-        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmask();
-        // Shift by 1 bit due to the discrepancy between
-        // android.hardware.radio.V1_0.RadioAccessFamily and the bitmask version of
-        // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
-        dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
-                dp.getBearerBitmask()) << 1;
-        dpi.mtu = dp.getMtuV4();
-        dpi.mvnoType = android.hardware.radio.V1_0.MvnoType.NONE;
-        dpi.mvnoMatchData = "";
-
-        return dpi;
-    }
-
-    /**
      * Convert to DataProfileInfo defined in radio/1.4/types.hal
      * @param dp Data profile
      * @return The converted DataProfileInfo
@@ -990,8 +968,8 @@
      * @param dp Data profile
      * @return The converted DataProfileInfo
      */
-    public static android.hardware.radio.data.DataProfileInfo convertToHalDataProfile(@Nullable
-            DataProfile dp) {
+    public static android.hardware.radio.data.DataProfileInfo convertToHalDataProfile(
+            @Nullable DataProfile dp) {
         if (dp == null) return null;
         android.hardware.radio.data.DataProfileInfo dpi =
                 new android.hardware.radio.data.DataProfileInfo();
@@ -1017,8 +995,11 @@
         dpi.persistent = dp.isPersistent();
         dpi.preferred = dp.isPreferred();
         dpi.alwaysOn = false;
+        dpi.infrastructureBitmap = android.hardware.radio.data.DataProfileInfo
+                .INFRASTRUCTURE_CELLULAR;
         if (dp.getApnSetting() != null) {
             dpi.alwaysOn = dp.getApnSetting().isAlwaysOn();
+            dpi.infrastructureBitmap = dp.getApnSetting().getInfrastructureBitmask();
         }
         dpi.trafficDescriptor = convertToHalTrafficDescriptorAidl(dp.getTrafficDescriptor());
 
@@ -1056,6 +1037,7 @@
                 .setRoamingProtocol(dpi.roamingProtocol)
                 .setUser(dpi.user)
                 .setAlwaysOn(dpi.alwaysOn)
+                .setInfrastructureBitmask(dpi.infrastructureBitmap)
                 .build();
 
         TrafficDescriptor td;
@@ -2198,21 +2180,15 @@
     }
 
     /**
-     * Convert LceDataInfo defined in radio/1.0/types.hal and LinkCapacityEstimate defined in
-     * radio/1.2, 1.6/types.hal to a list of LinkCapacityEstimates
-     * @param lceObj LceDataInfo defined in radio/1.0/types.hal or LinkCapacityEstimate defined in
-     *        radio/1.2, 1.6/types.hal
+     * Convert LinkCapacityEstimate defined in radio/1.2, 1.6/types.hal to
+     * a list of LinkCapacityEstimates
+     * @param lceObj LinkCapacityEstimate defined in radio/1.2, 1.6/types.hal
      * @return The converted list of LinkCapacityEstimates
      */
-    public static List<LinkCapacityEstimate> convertHalLceData(Object lceObj) {
+    public static List<LinkCapacityEstimate> convertHalLinkCapacityEstimate(Object lceObj) {
         final List<LinkCapacityEstimate> lceList = new ArrayList<>();
         if (lceObj == null) return lceList;
-        if (lceObj instanceof android.hardware.radio.V1_0.LceDataInfo) {
-            android.hardware.radio.V1_0.LceDataInfo lce =
-                    (android.hardware.radio.V1_0.LceDataInfo) lceObj;
-            lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_COMBINED,
-                    lce.lastHopCapacityKbps, LinkCapacityEstimate.INVALID));
-        } else if (lceObj instanceof android.hardware.radio.V1_2.LinkCapacityEstimate) {
+        if (lceObj instanceof android.hardware.radio.V1_2.LinkCapacityEstimate) {
             android.hardware.radio.V1_2.LinkCapacityEstimate lce =
                     (android.hardware.radio.V1_2.LinkCapacityEstimate) lceObj;
             lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_COMBINED,
@@ -2241,25 +2217,12 @@
     }
 
     /**
-     * Convert LceDataInfo defined in LceDataInfo.aidl to a list of LinkCapacityEstimates
-     * @param lce LceDataInfo defined in LceDataInfo.aidl
-     * @return The converted list of LinkCapacityEstimates
-     */
-    public static List<LinkCapacityEstimate> convertHalLceData(
-            android.hardware.radio.network.LceDataInfo lce) {
-        final List<LinkCapacityEstimate> lceList = new ArrayList<>();
-        lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_COMBINED,
-                lce.lastHopCapacityKbps, LinkCapacityEstimate.INVALID));
-        return lceList;
-    }
-
-    /**
      * Convert LinkCapacityEstimate defined in LinkCapacityEstimate.aidl to a list of
      * LinkCapacityEstimates
      * @param lce LinkCapacityEstimate defined in LinkCapacityEstimate.aidl
      * @return The converted list of LinkCapacityEstimates
      */
-    public static List<LinkCapacityEstimate> convertHalLceData(
+    public static List<LinkCapacityEstimate> convertHalLinkCapacityEstimate(
             android.hardware.radio.network.LinkCapacityEstimate lce) {
         final List<LinkCapacityEstimate> lceList = new ArrayList<>();
         int primaryDownlinkCapacityKbps = lce.downlinkCapacityKbps;
@@ -2283,9 +2246,9 @@
 
 
     /**
-     * Convert a list of CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal to a list of
+     * Convert a list of CellInfo defined in radio/1.4, 1.5, 1.6/types.hal to a list of
      * CellInfos
-     * @param records List of CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal
+     * @param records List of CellInfo defined in radio/1.4, 1.5, 1.6/types.hal
      * @return The converted list of CellInfos
      */
     public static ArrayList<CellInfo> convertHalCellInfoList(ArrayList<Object> records) {
@@ -2315,8 +2278,8 @@
     }
 
     /**
-     * Convert a CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal to CellInfo
-     * @param cellInfo CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal
+     * Convert a CellInfo defined in radio/1.4, 1.5, 1.6/types.hal to CellInfo
+     * @param cellInfo CellInfo defined in radio/1.4, 1.5, 1.6/types.hal
      * @param nanotime time the CellInfo was created
      * @return The converted CellInfo
      */
@@ -2338,87 +2301,7 @@
         CellSignalStrengthTdscdma tdscdmaSs = null;
         CellIdentityNr nrCi = null;
         CellSignalStrengthNr nrSs = null;
-        if (cellInfo instanceof android.hardware.radio.V1_0.CellInfo) {
-            final android.hardware.radio.V1_0.CellInfo record =
-                    (android.hardware.radio.V1_0.CellInfo) cellInfo;
-            connectionStatus = CellInfo.CONNECTION_UNKNOWN;
-            registered = record.registered;
-            switch (record.cellInfoType) {
-                case android.hardware.radio.V1_0.CellInfoType.GSM:
-                    type = CellInfo.TYPE_GSM;
-                    android.hardware.radio.V1_0.CellInfoGsm gsm = record.gsm.get(0);
-                    gsmCi = convertHalCellIdentityGsm(gsm.cellIdentityGsm);
-                    gsmSs = convertHalGsmSignalStrength(gsm.signalStrengthGsm);
-                    break;
-                case android.hardware.radio.V1_0.CellInfoType.CDMA:
-                    type = CellInfo.TYPE_CDMA;
-                    android.hardware.radio.V1_0.CellInfoCdma cdma = record.cdma.get(0);
-                    cdmaCi = convertHalCellIdentityCdma(cdma.cellIdentityCdma);
-                    cdmaSs = convertHalCdmaSignalStrength(
-                            cdma.signalStrengthCdma, cdma.signalStrengthEvdo);
-                    break;
-                case android.hardware.radio.V1_0.CellInfoType.LTE:
-                    type = CellInfo.TYPE_LTE;
-                    android.hardware.radio.V1_0.CellInfoLte lte = record.lte.get(0);
-                    lteCi = convertHalCellIdentityLte(lte.cellIdentityLte);
-                    lteSs = convertHalLteSignalStrength(lte.signalStrengthLte);
-                    lteCc = new CellConfigLte();
-                    break;
-                case android.hardware.radio.V1_0.CellInfoType.WCDMA:
-                    type = CellInfo.TYPE_WCDMA;
-                    android.hardware.radio.V1_0.CellInfoWcdma wcdma = record.wcdma.get(0);
-                    wcdmaCi = convertHalCellIdentityWcdma(wcdma.cellIdentityWcdma);
-                    wcdmaSs = convertHalWcdmaSignalStrength(wcdma.signalStrengthWcdma);
-                    break;
-                case android.hardware.radio.V1_0.CellInfoType.TD_SCDMA:
-                    type = CellInfo.TYPE_TDSCDMA;
-                    android.hardware.radio.V1_0.CellInfoTdscdma tdscdma = record.tdscdma.get(0);
-                    tdscdmaCi = convertHalCellIdentityTdscdma(tdscdma.cellIdentityTdscdma);
-                    tdscdmaSs = convertHalTdscdmaSignalStrength(tdscdma.signalStrengthTdscdma);
-                    break;
-                default: return null;
-            }
-        } else if (cellInfo instanceof android.hardware.radio.V1_2.CellInfo) {
-            final android.hardware.radio.V1_2.CellInfo record =
-                    (android.hardware.radio.V1_2.CellInfo) cellInfo;
-            connectionStatus = record.connectionStatus;
-            registered = record.registered;
-            switch(record.cellInfoType) {
-                case android.hardware.radio.V1_0.CellInfoType.GSM:
-                    type = CellInfo.TYPE_GSM;
-                    android.hardware.radio.V1_2.CellInfoGsm gsm = record.gsm.get(0);
-                    gsmCi = convertHalCellIdentityGsm(gsm.cellIdentityGsm);
-                    gsmSs = convertHalGsmSignalStrength(gsm.signalStrengthGsm);
-                    break;
-                case android.hardware.radio.V1_0.CellInfoType.CDMA:
-                    type = CellInfo.TYPE_CDMA;
-                    android.hardware.radio.V1_2.CellInfoCdma cdma = record.cdma.get(0);
-                    cdmaCi = convertHalCellIdentityCdma(cdma.cellIdentityCdma);
-                    cdmaSs = convertHalCdmaSignalStrength(
-                            cdma.signalStrengthCdma, cdma.signalStrengthEvdo);
-                    break;
-                case android.hardware.radio.V1_0.CellInfoType.LTE:
-                    type = CellInfo.TYPE_LTE;
-                    android.hardware.radio.V1_2.CellInfoLte lte = record.lte.get(0);
-                    lteCi = convertHalCellIdentityLte(lte.cellIdentityLte);
-                    lteSs = convertHalLteSignalStrength(lte.signalStrengthLte);
-                    lteCc = new CellConfigLte();
-                    break;
-                case android.hardware.radio.V1_0.CellInfoType.WCDMA:
-                    type = CellInfo.TYPE_WCDMA;
-                    android.hardware.radio.V1_2.CellInfoWcdma wcdma = record.wcdma.get(0);
-                    wcdmaCi = convertHalCellIdentityWcdma(wcdma.cellIdentityWcdma);
-                    wcdmaSs = convertHalWcdmaSignalStrength(wcdma.signalStrengthWcdma);
-                    break;
-                case android.hardware.radio.V1_0.CellInfoType.TD_SCDMA:
-                    type = CellInfo.TYPE_TDSCDMA;
-                    android.hardware.radio.V1_2.CellInfoTdscdma tdscdma = record.tdscdma.get(0);
-                    tdscdmaCi = convertHalCellIdentityTdscdma(tdscdma.cellIdentityTdscdma);
-                    tdscdmaSs = convertHalTdscdmaSignalStrength(tdscdma.signalStrengthTdscdma);
-                    break;
-                default: return null;
-            }
-        } else if (cellInfo instanceof android.hardware.radio.V1_4.CellInfo) {
+        if (cellInfo instanceof android.hardware.radio.V1_4.CellInfo) {
             final android.hardware.radio.V1_4.CellInfo record =
                     (android.hardware.radio.V1_4.CellInfo) cellInfo;
             connectionStatus = record.connectionStatus;
@@ -2648,43 +2531,13 @@
     }
 
     /**
-     * Convert a CellIdentity defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentity
-     * @param halCi CellIdentity defined in radio/1.0, 1.2, 1.5/types.hal
+     * Convert a CellIdentity defined in radio/1.2, 1.5/types.hal to CellIdentity
+     * @param halCi CellIdentity defined in radio/1.2, 1.5/types.hal
      * @return The converted CellIdentity
      */
     public static CellIdentity convertHalCellIdentity(Object halCi) {
         if (halCi == null) return null;
-        if (halCi instanceof android.hardware.radio.V1_0.CellIdentity) {
-            android.hardware.radio.V1_0.CellIdentity ci =
-                    (android.hardware.radio.V1_0.CellIdentity) halCi;
-            switch (ci.cellInfoType) {
-                case CellInfo.TYPE_GSM:
-                    if (ci.cellIdentityGsm.size() == 1) {
-                        return convertHalCellIdentityGsm(ci.cellIdentityGsm.get(0));
-                    }
-                    break;
-                case CellInfo.TYPE_CDMA:
-                    if (ci.cellIdentityCdma.size() == 1) {
-                        return convertHalCellIdentityCdma(ci.cellIdentityCdma.get(0));
-                    }
-                    break;
-                case CellInfo.TYPE_LTE:
-                    if (ci.cellIdentityLte.size() == 1) {
-                        return convertHalCellIdentityLte(ci.cellIdentityLte.get(0));
-                    }
-                    break;
-                case CellInfo.TYPE_WCDMA:
-                    if (ci.cellIdentityWcdma.size() == 1) {
-                        return convertHalCellIdentityWcdma(ci.cellIdentityWcdma.get(0));
-                    }
-                    break;
-                case CellInfo.TYPE_TDSCDMA:
-                    if (ci.cellIdentityTdscdma.size() == 1) {
-                        return convertHalCellIdentityTdscdma(ci.cellIdentityTdscdma.get(0));
-                    }
-                    break;
-            }
-        } else if (halCi instanceof android.hardware.radio.V1_2.CellIdentity) {
+        if (halCi instanceof android.hardware.radio.V1_2.CellIdentity) {
             android.hardware.radio.V1_2.CellIdentity ci =
                     (android.hardware.radio.V1_2.CellIdentity) halCi;
             switch (ci.cellInfoType) {
@@ -2761,19 +2614,13 @@
     }
 
     /**
-     * Convert a CellIdentityGsm defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentityGsm
-     * @param gsm CellIdentityGsm defined in radio/1.0, 1.2, 1.5/types.hal
+     * Convert a CellIdentityGsm defined in radio/1.2, 1.5/types.hal to CellIdentityGsm
+     * @param gsm CellIdentityGsm defined in radio/1.2, 1.5/types.hal
      * @return The converted CellIdentityGsm
      */
     public static CellIdentityGsm convertHalCellIdentityGsm(Object gsm) {
         if (gsm == null) return null;
-        if (gsm instanceof android.hardware.radio.V1_0.CellIdentityGsm) {
-            android.hardware.radio.V1_0.CellIdentityGsm ci =
-                    (android.hardware.radio.V1_0.CellIdentityGsm) gsm;
-            return new CellIdentityGsm(ci.lac, ci.cid, ci.arfcn,
-                    ci.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : ci.bsic, ci.mcc, ci.mnc, "", "",
-                    new ArraySet<>());
-        } else if (gsm instanceof android.hardware.radio.V1_2.CellIdentityGsm) {
+        if (gsm instanceof android.hardware.radio.V1_2.CellIdentityGsm) {
             android.hardware.radio.V1_2.CellIdentityGsm ci =
                     (android.hardware.radio.V1_2.CellIdentityGsm) gsm;
             return new CellIdentityGsm(ci.base.lac, ci.base.cid, ci.base.arfcn,
@@ -2806,18 +2653,13 @@
     }
 
     /**
-     * Convert a CellIdentityCdma defined in radio/1.0, 1.2/types.hal to CellIdentityCdma
-     * @param cdma CellIdentityCdma defined in radio/1.0, 1.2/types.hal
+     * Convert a CellIdentityCdma defined in radio/1.2/types.hal to CellIdentityCdma
+     * @param cdma CellIdentityCdma defined in radio/1.2/types.hal
      * @return The converted CellIdentityCdma
      */
     public static CellIdentityCdma convertHalCellIdentityCdma(Object cdma) {
         if (cdma == null) return null;
-        if (cdma instanceof android.hardware.radio.V1_0.CellIdentityCdma) {
-            android.hardware.radio.V1_0.CellIdentityCdma ci =
-                    (android.hardware.radio.V1_0.CellIdentityCdma) cdma;
-            return new CellIdentityCdma(ci.networkId, ci.systemId, ci.baseStationId, ci.longitude,
-                    ci.latitude, "", "");
-        } else if (cdma instanceof android.hardware.radio.V1_2.CellIdentityCdma) {
+        if (cdma instanceof android.hardware.radio.V1_2.CellIdentityCdma) {
             android.hardware.radio.V1_2.CellIdentityCdma ci =
                     (android.hardware.radio.V1_2.CellIdentityCdma) cdma;
             return new CellIdentityCdma(ci.base.networkId, ci.base.systemId, ci.base.baseStationId,
@@ -2840,18 +2682,13 @@
     }
 
     /**
-     * Convert a CellIdentityLte defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentityLte
-     * @param lte CellIdentityLte defined in radio/1.0, 1.2, 1.5/types.hal
+     * Convert a CellIdentityLte defined in radio/1.2, 1.5/types.hal to CellIdentityLte
+     * @param lte CellIdentityLte defined in radio/1.2, 1.5/types.hal
      * @return The converted CellIdentityLte
      */
     public static CellIdentityLte convertHalCellIdentityLte(Object lte) {
         if (lte == null) return null;
-        if (lte instanceof android.hardware.radio.V1_0.CellIdentityLte) {
-            android.hardware.radio.V1_0.CellIdentityLte ci =
-                    (android.hardware.radio.V1_0.CellIdentityLte) lte;
-            return new CellIdentityLte(ci.ci, ci.pci, ci.tac, ci.earfcn, new int[] {},
-                    CellInfo.UNAVAILABLE, ci.mcc, ci.mnc, "", "", new ArraySet<>(), null);
-        } else if (lte instanceof android.hardware.radio.V1_2.CellIdentityLte) {
+        if (lte instanceof android.hardware.radio.V1_2.CellIdentityLte) {
             android.hardware.radio.V1_2.CellIdentityLte ci =
                     (android.hardware.radio.V1_2.CellIdentityLte) lte;
             return new CellIdentityLte(ci.base.ci, ci.base.pci, ci.base.tac, ci.base.earfcn,
@@ -2885,18 +2722,13 @@
     }
 
     /**
-     * Convert a CellIdentityWcdma defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentityWcdma
-     * @param wcdma CellIdentityWcdma defined in radio/1.0, 1.2, 1.5/types.hal
+     * Convert a CellIdentityWcdma defined in radio/1.2, 1.5/types.hal to CellIdentityWcdma
+     * @param wcdma CellIdentityWcdma defined in radio/1.2, 1.5/types.hal
      * @return The converted CellIdentityWcdma
      */
     public static CellIdentityWcdma convertHalCellIdentityWcdma(Object wcdma) {
         if (wcdma == null) return null;
-        if (wcdma instanceof android.hardware.radio.V1_0.CellIdentityWcdma) {
-            android.hardware.radio.V1_0.CellIdentityWcdma ci =
-                    (android.hardware.radio.V1_0.CellIdentityWcdma) wcdma;
-            return new CellIdentityWcdma(ci.lac, ci.cid, ci.psc, ci.uarfcn, ci.mcc, ci.mnc, "", "",
-                    new ArraySet<>(), null);
-        } else if (wcdma instanceof android.hardware.radio.V1_2.CellIdentityWcdma) {
+        if (wcdma instanceof android.hardware.radio.V1_2.CellIdentityWcdma) {
             android.hardware.radio.V1_2.CellIdentityWcdma ci =
                     (android.hardware.radio.V1_2.CellIdentityWcdma) wcdma;
             return new CellIdentityWcdma(ci.base.lac, ci.base.cid, ci.base.psc, ci.base.uarfcn,
@@ -2928,18 +2760,13 @@
     }
 
     /**
-     * Convert a CellIdentityTdscdma defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentityTdscdma
-     * @param tdscdma CellIdentityTdscdma defined in radio/1.0, 1.2, 1.5/types.hal
+     * Convert a CellIdentityTdscdma defined in radio/1.2, 1.5/types.hal to CellIdentityTdscdma
+     * @param tdscdma CellIdentityTdscdma defined in radio/1.2, 1.5/types.hal
      * @return The converted CellIdentityTdscdma
      */
     public static CellIdentityTdscdma convertHalCellIdentityTdscdma(Object tdscdma) {
         if (tdscdma == null) return null;
-        if (tdscdma instanceof android.hardware.radio.V1_0.CellIdentityTdscdma) {
-            android.hardware.radio.V1_0.CellIdentityTdscdma ci =
-                    (android.hardware.radio.V1_0.CellIdentityTdscdma) tdscdma;
-            return new CellIdentityTdscdma(ci.mcc, ci.mnc, ci.lac, ci.cid, ci.cpid,
-                    CellInfo.UNAVAILABLE, "", "", Collections.emptyList(), null);
-        } else if (tdscdma instanceof android.hardware.radio.V1_2.CellIdentityTdscdma) {
+        if (tdscdma instanceof android.hardware.radio.V1_2.CellIdentityTdscdma) {
             android.hardware.radio.V1_2.CellIdentityTdscdma ci =
                     (android.hardware.radio.V1_2.CellIdentityTdscdma) tdscdma;
             return new CellIdentityTdscdma(ci.base.mcc, ci.base.mnc, ci.base.lac, ci.base.cid,
@@ -2959,7 +2786,7 @@
 
     /**
      * Convert a CellIdentityTdscdma defined in CellIdentityTdscdma.aidl to CellIdentityTdscdma
-     * @param cid CellIdentityTdscdma defined in radio/1.0, 1.2, 1.5/types.hal
+     * @param cid CellIdentityTdscdma defined in radio/1.2, 1.5/types.hal
      * @return The converted CellIdentityTdscdma
      */
     public static CellIdentityTdscdma convertHalCellIdentityTdscdma(
@@ -3008,31 +2835,13 @@
     }
 
     /**
-     * Convert a SignalStrength defined in radio/1.0, 1.2, 1.4, 1.6/types.hal to SignalStrength
-     * @param ss SignalStrength defined in radio/1.0, 1.2, 1.4, 1.6/types.hal
+     * Convert a SignalStrength defined in radio/1.4, 1.6/types.hal to SignalStrength
+     * @param ss SignalStrength defined in radio/1.4, 1.6/types.hal
      * @return The converted SignalStrength
      */
     public static SignalStrength convertHalSignalStrength(Object ss) {
         if (ss == null) return null;
-        if (ss instanceof android.hardware.radio.V1_0.SignalStrength) {
-            android.hardware.radio.V1_0.SignalStrength signalStrength =
-                    (android.hardware.radio.V1_0.SignalStrength) ss;
-            return new SignalStrength(
-                    convertHalCdmaSignalStrength(signalStrength.cdma, signalStrength.evdo),
-                    convertHalGsmSignalStrength(signalStrength.gw), new CellSignalStrengthWcdma(),
-                    convertHalTdscdmaSignalStrength(signalStrength.tdScdma),
-                    convertHalLteSignalStrength(signalStrength.lte),
-                    new CellSignalStrengthNr());
-        } else if (ss instanceof android.hardware.radio.V1_2.SignalStrength) {
-            android.hardware.radio.V1_2.SignalStrength signalStrength =
-                    (android.hardware.radio.V1_2.SignalStrength) ss;
-            return new SignalStrength(
-                    convertHalCdmaSignalStrength(signalStrength.cdma, signalStrength.evdo),
-                    convertHalGsmSignalStrength(signalStrength.gsm),
-                    convertHalWcdmaSignalStrength(signalStrength.wcdma),
-                    convertHalTdscdmaSignalStrength(signalStrength.tdScdma),
-                    convertHalLteSignalStrength(signalStrength.lte), new CellSignalStrengthNr());
-        } else if (ss instanceof android.hardware.radio.V1_4.SignalStrength) {
+        if (ss instanceof android.hardware.radio.V1_4.SignalStrength) {
             android.hardware.radio.V1_4.SignalStrength signalStrength =
                     (android.hardware.radio.V1_4.SignalStrength) ss;
             return new SignalStrength(
@@ -3180,29 +2989,19 @@
     }
 
     /**
-     * Convert a WcdmaSignalStrength defined in radio/1.0, 1.2/types.hal to CellSignalStrengthWcdma
-     * @param wcdma WcdmaSignalStrength defined in radio/1.0, 1.2/types.hal
+     * Convert a WcdmaSignalStrength defined in radio/1.2/types.hal to CellSignalStrengthWcdma
+     * @param wcdma WcdmaSignalStrength defined in radio/1.2/types.hal
      * @return The converted CellSignalStrengthWcdma
      */
     public static CellSignalStrengthWcdma convertHalWcdmaSignalStrength(Object wcdma) {
         if (wcdma == null) return null;
-        CellSignalStrengthWcdma ret = null;
-        if (wcdma instanceof android.hardware.radio.V1_0.WcdmaSignalStrength) {
-            android.hardware.radio.V1_0.WcdmaSignalStrength ss =
-                    (android.hardware.radio.V1_0.WcdmaSignalStrength) wcdma;
-            ret = new CellSignalStrengthWcdma(
-                    CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength), ss.bitErrorRate,
-                    CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE);
-        } else if (wcdma instanceof android.hardware.radio.V1_2.WcdmaSignalStrength) {
-            android.hardware.radio.V1_2.WcdmaSignalStrength ss =
-                    (android.hardware.radio.V1_2.WcdmaSignalStrength) wcdma;
-            ret = new CellSignalStrengthWcdma(
-                    CellSignalStrength.getRssiDbmFromAsu(ss.base.signalStrength),
-                    ss.base.bitErrorRate, CellSignalStrength.getRscpDbmFromAsu(ss.rscp),
-                    CellSignalStrength.getEcNoDbFromAsu(ss.ecno));
-        }
-        if (ret != null && ret.getRssi() == CellInfo.UNAVAILABLE
-                && ret.getRscp() == CellInfo.UNAVAILABLE) {
+        android.hardware.radio.V1_2.WcdmaSignalStrength ss =
+                (android.hardware.radio.V1_2.WcdmaSignalStrength) wcdma;
+        CellSignalStrengthWcdma ret = new CellSignalStrengthWcdma(
+                CellSignalStrength.getRssiDbmFromAsu(ss.base.signalStrength),
+                ss.base.bitErrorRate, CellSignalStrength.getRscpDbmFromAsu(ss.rscp),
+                CellSignalStrength.getEcNoDbFromAsu(ss.ecno));
+        if (ret.getRssi() == CellInfo.UNAVAILABLE && ret.getRscp() == CellInfo.UNAVAILABLE) {
             ret.setDefaultValues();
             ret.updateLevel(null, null);
         }
@@ -3228,29 +3027,18 @@
     }
 
     /**
-     * Convert a TdScdmaSignalStrength defined in radio/1.0/types.hal or TdscdmaSignalStrength
-     * defined in radio/1.2/types.hal to CellSignalStrengthTdscdma
-     * @param tdscdma TdScdmaSignalStrength defined in radio/1.0/types.hal or TdscdmaSignalStrength
-     *        defined in radio/1.2/types.hal
+     * Convert a TdscdmaSignalStrength defined in radio/1.2/types.hal to CellSignalStrengthTdscdma
+     * @param tdscdma TdscdmaSignalStrength defined in radio/1.2/types.hal
      * @return The converted CellSignalStrengthTdscdma
      */
     public static CellSignalStrengthTdscdma convertHalTdscdmaSignalStrength(Object tdscdma) {
         if (tdscdma == null) return null;
-        CellSignalStrengthTdscdma ret = null;
-        if (tdscdma instanceof android.hardware.radio.V1_0.TdScdmaSignalStrength) {
-            android.hardware.radio.V1_0.TdScdmaSignalStrength ss =
-                    (android.hardware.radio.V1_0.TdScdmaSignalStrength) tdscdma;
-            ret = new CellSignalStrengthTdscdma(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
-                    ss.rscp != CellInfo.UNAVAILABLE ? -ss.rscp : ss.rscp);
-        } else if (tdscdma instanceof android.hardware.radio.V1_2.TdscdmaSignalStrength) {
-            android.hardware.radio.V1_2.TdscdmaSignalStrength ss =
-                    (android.hardware.radio.V1_2.TdscdmaSignalStrength) tdscdma;
-            ret = new CellSignalStrengthTdscdma(
-                    CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength), ss.bitErrorRate,
-                    CellSignalStrength.getRscpDbmFromAsu(ss.rscp));
-        }
-        if (ret != null && ret.getRssi() == CellInfo.UNAVAILABLE
-                && ret.getRscp() == CellInfo.UNAVAILABLE) {
+        android.hardware.radio.V1_2.TdscdmaSignalStrength ss =
+                (android.hardware.radio.V1_2.TdscdmaSignalStrength) tdscdma;
+        CellSignalStrengthTdscdma ret = new CellSignalStrengthTdscdma(
+                CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength), ss.bitErrorRate,
+                CellSignalStrength.getRscpDbmFromAsu(ss.rscp));
+        if (ret.getRssi() == CellInfo.UNAVAILABLE && ret.getRscp() == CellInfo.UNAVAILABLE) {
             ret.setDefaultValues();
             ret.updateLevel(null, null);
         }
@@ -3435,9 +3223,9 @@
     }
 
     /**
-     * Convert SetupDataCallResult defined in radio/1.0, 1.4, 1.5, 1.6/types.hal into
+     * Convert SetupDataCallResult defined in radio/1.4, 1.5, 1.6/types.hal into
      * DataCallResponse
-     * @param dcResult SetupDataCallResult defined in radio/1.0, 1.4, 1.5, 1.6/types.hal
+     * @param dcResult SetupDataCallResult defined in radio/1.4, 1.5, 1.6/types.hal
      * @return The converted DataCallResponse
      */
     @VisibleForTesting
@@ -3448,10 +3236,10 @@
         long suggestedRetryTime;
         String ifname;
         int protocolType;
-        String[] addresses = null;
-        String[] dnses = null;
-        String[] gateways = null;
-        String[] pcscfs = null;
+        String[] addresses;
+        String[] dnses;
+        String[] gateways;
+        String[] pcscfs;
         Qos defaultQos = null;
         @DataCallResponse.HandoverFailureMode
         int handoverFailureMode = DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
@@ -3461,34 +3249,7 @@
         NetworkSliceInfo sliceInfo = null;
         List<TrafficDescriptor> trafficDescriptors = new ArrayList<>();
 
-        if (dcResult instanceof android.hardware.radio.V1_0.SetupDataCallResult) {
-            final android.hardware.radio.V1_0.SetupDataCallResult result =
-                    (android.hardware.radio.V1_0.SetupDataCallResult) dcResult;
-            cause = result.status;
-            suggestedRetryTime = result.suggestedRetryTime;
-            cid = result.cid;
-            active = result.active;
-            protocolType = ApnSetting.getProtocolIntFromString(result.type);
-            ifname = result.ifname;
-            if (!TextUtils.isEmpty(result.addresses)) {
-                addresses = result.addresses.split("\\s+");
-            }
-            if (!TextUtils.isEmpty(result.dnses)) {
-                dnses = result.dnses.split("\\s+");
-            }
-            if (!TextUtils.isEmpty(result.gateways)) {
-                gateways = result.gateways.split("\\s+");
-            }
-            if (!TextUtils.isEmpty(result.pcscf)) {
-                pcscfs = result.pcscf.split("\\s+");
-            }
-            mtu = mtuV4 = mtuV6 = result.mtu;
-            if (addresses != null) {
-                for (String address : addresses) {
-                    laList.add(convertToLinkAddress(address));
-                }
-            }
-        } else if (dcResult instanceof android.hardware.radio.V1_4.SetupDataCallResult) {
+        if (dcResult instanceof android.hardware.radio.V1_4.SetupDataCallResult) {
             final android.hardware.radio.V1_4.SetupDataCallResult result =
                     (android.hardware.radio.V1_4.SetupDataCallResult) dcResult;
             cause = result.cause;
@@ -3517,7 +3278,7 @@
             protocolType = result.type;
             ifname = result.ifname;
             laList = result.addresses.stream().map(la -> convertToLinkAddress(
-                    la.address, la.properties, la.deprecationTime, la.expirationTime))
+                            la.address, la.properties, la.deprecationTime, la.expirationTime))
                     .collect(Collectors.toList());
             dnses = result.dnses.toArray(new String[0]);
             gateways = result.gateways.toArray(new String[0]);
@@ -3535,7 +3296,7 @@
             protocolType = result.type;
             ifname = result.ifname;
             laList = result.addresses.stream().map(la -> convertToLinkAddress(
-                    la.address, la.properties, la.deprecationTime, la.expirationTime))
+                            la.address, la.properties, la.deprecationTime, la.expirationTime))
                     .collect(Collectors.toList());
             dnses = result.dnses.toArray(new String[0]);
             gateways = result.gateways.toArray(new String[0]);
@@ -3803,23 +3564,24 @@
     public static NetworkSlicingConfig convertHalSlicingConfig(
             android.hardware.radio.V1_6.SlicingConfig sc) {
         List<UrspRule> urspRules = sc.urspRules.stream().map(ur -> new UrspRule(ur.precedence,
-                ur.trafficDescriptors.stream()
-                        .map(td -> {
-                            try {
-                                return convertHalTrafficDescriptor(td);
-                            } catch (IllegalArgumentException e) {
-                                loge("convertHalSlicingConfig: Failed to convert traffic descriptor"
-                                        + ". e=" + e);
-                                return null;
-                            }
-                        })
-                        .filter(Objects::nonNull)
-                        .collect(Collectors.toList()),
-                ur.routeSelectionDescriptor.stream().map(rsd -> new RouteSelectionDescriptor(
-                        rsd.precedence, rsd.sessionType.value(), rsd.sscMode.value(),
-                        rsd.sliceInfo.stream().map(RILUtils::convertHalSliceInfo)
+                        ur.trafficDescriptors.stream()
+                                .map(td -> {
+                                    try {
+                                        return convertHalTrafficDescriptor(td);
+                                    } catch (IllegalArgumentException e) {
+                                        loge("convertHalSlicingConfig: Failed to convert traffic "
+                                                + "descriptor. e=" + e);
+                                        return null;
+                                    }
+                                })
+                                .filter(Objects::nonNull)
                                 .collect(Collectors.toList()),
-                        rsd.dnn)).collect(Collectors.toList())))
+                        ur.routeSelectionDescriptor.stream().map(
+                                rsd -> new RouteSelectionDescriptor(rsd.precedence,
+                                        rsd.sessionType.value(), rsd.sscMode.value(),
+                                        rsd.sliceInfo.stream().map(RILUtils::convertHalSliceInfo)
+                                                .collect(Collectors.toList()),
+                                        rsd.dnn)).collect(Collectors.toList())))
                 .collect(Collectors.toList());
         return new NetworkSlicingConfig(urspRules, sc.sliceInfo.stream()
                 .map(RILUtils::convertHalSliceInfo).collect(Collectors.toList()));
@@ -4040,10 +3802,10 @@
     }
 
     /**
-     * Convert a list of SetupDataCallResult defined in radio/1.0, 1.4, 1.5, 1.6/types.hal into
+     * Convert a list of SetupDataCallResult defined in radio/1.4, 1.5, 1.6/types.hal into
      * a list of DataCallResponse
      * @param dataCallResultList List of SetupDataCallResult defined in
-     *        radio/1.0, 1.4, 1.5, 1.6/types.hal
+     *        radio/1.4, 1.5, 1.6/types.hal
      * @return The converted list of DataCallResponses
      */
     @VisibleForTesting
@@ -4133,8 +3895,8 @@
     }
 
     /**
-     * Convert Call defined in radio/1.0, 1.2, 1.6/types.hal to DriverCall
-     * @param halCall Call defined in radio/1.0, 1.2, 1.6/types.hal
+     * Convert Call defined in radio/1.2, 1.6/types.hal to DriverCall
+     * @param halCall Call defined in radio/1.2, 1.6/types.hal
      * @return The converted DriverCall
      */
     public static DriverCall convertToDriverCall(Object halCall) {
@@ -4150,17 +3912,13 @@
             call16 = null;
             call12 = (android.hardware.radio.V1_2.Call) halCall;
             call10 = call12.base;
-        } else if (halCall instanceof android.hardware.radio.V1_0.Call) {
-            call16 = null;
-            call12 = null;
-            call10 = (android.hardware.radio.V1_0.Call) halCall;
         } else {
             call16 = null;
             call12 = null;
             call10 = null;
         }
         if (call10 != null) {
-            dc.state = DriverCall.stateFromCLCC((int) (call10.state));
+            dc.state = DriverCall.stateFromCLCC(call10.state);
             dc.index = call10.index;
             dc.TOA = call10.toa;
             dc.isMpty = call10.isMpty;
@@ -4169,10 +3927,9 @@
             dc.isVoice = call10.isVoice;
             dc.isVoicePrivacy = call10.isVoicePrivacy;
             dc.number = call10.number;
-            dc.numberPresentation = DriverCall.presentationFromCLIP(
-                    (int) (call10.numberPresentation));
+            dc.numberPresentation = DriverCall.presentationFromCLIP(call10.numberPresentation);
             dc.name = call10.name;
-            dc.namePresentation = DriverCall.presentationFromCLIP((int) (call10.namePresentation));
+            dc.namePresentation = DriverCall.presentationFromCLIP(call10.namePresentation);
             if (call10.uusInfo.size() == 1) {
                 dc.uusInfo = new UUSInfo();
                 dc.uusInfo.setType(call10.uusInfo.get(0).uusType);
@@ -4186,7 +3943,7 @@
             dc.number = PhoneNumberUtils.stringFromStringAndTOA(dc.number, dc.TOA);
         }
         if (call12 != null) {
-            dc.audioQuality = (int) (call12.audioQuality);
+            dc.audioQuality = call12.audioQuality;
         }
         if (call16 != null) {
             dc.forwardedNumber = call16.forwardedNumber;
@@ -4201,7 +3958,7 @@
      */
     public static DriverCall convertToDriverCall(android.hardware.radio.voice.Call halCall) {
         DriverCall dc = new DriverCall();
-        dc.state = DriverCall.stateFromCLCC((int) halCall.state);
+        dc.state = DriverCall.stateFromCLCC(halCall.state);
         dc.index = halCall.index;
         dc.TOA = halCall.toa;
         dc.isMpty = halCall.isMpty;
@@ -4210,9 +3967,9 @@
         dc.isVoice = halCall.isVoice;
         dc.isVoicePrivacy = halCall.isVoicePrivacy;
         dc.number = halCall.number;
-        dc.numberPresentation = DriverCall.presentationFromCLIP((int) halCall.numberPresentation);
+        dc.numberPresentation = DriverCall.presentationFromCLIP(halCall.numberPresentation);
         dc.name = halCall.name;
-        dc.namePresentation = DriverCall.presentationFromCLIP((int) halCall.namePresentation);
+        dc.namePresentation = DriverCall.presentationFromCLIP(halCall.namePresentation);
         if (halCall.uusInfo.length == 1) {
             dc.uusInfo = new UUSInfo();
             dc.uusInfo.setType(halCall.uusInfo[0].uusType);
@@ -4223,7 +3980,7 @@
         }
         // Make sure there's a leading + on addresses with a TOA of 145
         dc.number = PhoneNumberUtils.stringFromStringAndTOA(dc.number, dc.TOA);
-        dc.audioQuality = (int) halCall.audioQuality;
+        dc.audioQuality = halCall.audioQuality;
         dc.forwardedNumber = halCall.forwardedNumber;
         return dc;
     }
@@ -4283,6 +4040,9 @@
     public static List<CarrierIdentifier> convertHalCarrierList(
             android.hardware.radio.sim.Carrier[] carrierList) {
         List<CarrierIdentifier> ret = new ArrayList<>();
+        if (carrierList == null) {
+            return ret;
+        }
         for (int i = 0; i < carrierList.length; i++) {
             String mcc = carrierList[i].mcc;
             String mnc = carrierList[i].mnc;
@@ -4304,6 +4064,85 @@
     }
 
     /**
+     * Convert an array of CarrierInfo defined in
+     * radio/aidl/android/hardware/radio/sim/CarrierInfo.aidl to a list of CarrierInfo
+     * defined in android/service/carrier/CarrierInfo.java
+     *
+     * @param carrierInfos array of CarrierInfo defined in
+     *                     radio/aidl/android/hardware/radio/sim/CarrierInfo.aidl
+     * @return The converted list of CarrierInfo
+     */
+    public static List<CarrierInfo> convertAidlCarrierInfoList(
+            android.hardware.radio.sim.CarrierInfo[] carrierInfos) {
+        List<CarrierInfo> carrierInfoList = new ArrayList<>();
+        if (carrierInfos == null) {
+            loge("convertAidlCarrierInfoList received NULL carrierInfos");
+            return carrierInfoList;
+        }
+        for (int index = 0; index < carrierInfos.length; index++) {
+            String mcc = carrierInfos[index].mcc;
+            String mnc = carrierInfos[index].mnc;
+            String spn = carrierInfos[index].spn;
+            String gid1 = carrierInfos[index].gid1;
+            String gid2 = carrierInfos[index].gid2;
+            String imsi = carrierInfos[index].imsiPrefix;
+            String iccid = carrierInfos[index].iccid;
+            String impi = carrierInfos[index].impi;
+            List<android.hardware.radio.sim.Plmn> halEhplmn = carrierInfos[index].ehplmn;
+            List<String> eHplmnList = new ArrayList<>();
+            if (halEhplmn != null) {
+                for (int plmnIndex = 0; plmnIndex < halEhplmn.size(); plmnIndex++) {
+                    String ehplmnMcc = halEhplmn.get(plmnIndex).mcc;
+                    String ehplmnMnc = halEhplmn.get(plmnIndex).mnc;
+                    eHplmnList.add(ehplmnMcc + "," + ehplmnMnc);
+                }
+            } else {
+                loge("convertAidlCarrierInfoList ehplmList is NULL");
+            }
+            CarrierInfo carrierInfo = new CarrierInfo(mcc, mnc, spn, gid1, gid2, imsi, iccid, impi,
+                    eHplmnList);
+            carrierInfoList.add(carrierInfo);
+        }
+        return carrierInfoList;
+    }
+
+    /**
+     * Convert the sim policy defined in
+     * radio/aidl/android/hardware/radio/sim/SimLockMultiSimPolicy.aidl to the equivalent sim
+     * policy defined in android.telephony/CarrierRestrictionRules.MultiSimPolicy
+     *
+     * @param multiSimPolicy of type defined in SimLockMultiSimPolicy.aidl
+     * @return int of type CarrierRestrictionRules.MultiSimPolicy
+     */
+    public static @CarrierRestrictionRules.MultiSimPolicy int convertAidlSimLockMultiSimPolicy(
+            int multiSimPolicy) {
+        switch (multiSimPolicy) {
+            case android.hardware.radio.sim.SimLockMultiSimPolicy.ONE_VALID_SIM_MUST_BE_PRESENT:
+                return CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT;
+            case android.hardware.radio.sim.SimLockMultiSimPolicy.APPLY_TO_ALL_SLOTS:
+                return CarrierRestrictionRules.MULTISIM_POLICY_APPLY_TO_ALL_SLOTS;
+            case android.hardware.radio.sim.SimLockMultiSimPolicy.APPLY_TO_ONLY_SLOT_1:
+                return CarrierRestrictionRules.MULTISIM_POLICY_APPLY_TO_ONLY_SLOT_1;
+            case android.hardware.radio.sim.SimLockMultiSimPolicy.VALID_SIM_MUST_PRESENT_ON_SLOT_1:
+                return CarrierRestrictionRules.MULTISIM_POLICY_VALID_SIM_MUST_PRESENT_ON_SLOT_1;
+            case android.hardware.radio.sim.SimLockMultiSimPolicy.
+                    ACTIVE_SERVICE_ON_SLOT_1_TO_UNBLOCK_OTHER_SLOTS:
+                return CarrierRestrictionRules.
+                        MULTISIM_POLICY_ACTIVE_SERVICE_ON_SLOT_1_TO_UNBLOCK_OTHER_SLOTS;
+            case android.hardware.radio.sim.SimLockMultiSimPolicy.
+                    ACTIVE_SERVICE_ON_ANY_SLOT_TO_UNBLOCK_OTHER_SLOTS:
+                return CarrierRestrictionRules.
+                        MULTISIM_POLICY_ACTIVE_SERVICE_ON_ANY_SLOT_TO_UNBLOCK_OTHER_SLOTS;
+            case android.hardware.radio.sim.SimLockMultiSimPolicy.ALL_SIMS_MUST_BE_VALID:
+                return CarrierRestrictionRules.MULTISIM_POLICY_ALL_SIMS_MUST_BE_VALID;
+            case android.hardware.radio.sim.SimLockMultiSimPolicy.SLOT_POLICY_OTHER:
+                return CarrierRestrictionRules.MULTISIM_POLICY_SLOT_POLICY_OTHER;
+            default:
+                return CarrierRestrictionRules.MULTISIM_POLICY_NONE;
+        }
+    }
+
+    /**
      * Convert CardStatus defined in radio/1.0, 1.5/types.hal to IccCardStatus
      * @param cardStatus CardStatus defined in radio/1.0, 1.5/types.hal
      * @return The converted IccCardStatus
@@ -4634,14 +4473,20 @@
     public static PhoneCapability convertHalPhoneCapability(int[] deviceNrCapabilities, Object o) {
         int maxActiveVoiceCalls = 0;
         int maxActiveData = 0;
-        int maxActiveInternetData = 0;
         boolean validationBeforeSwitchSupported = false;
         List<ModemInfo> logicalModemList = new ArrayList<>();
         if (o instanceof android.hardware.radio.config.PhoneCapability) {
             final android.hardware.radio.config.PhoneCapability phoneCapability =
                     (android.hardware.radio.config.PhoneCapability) o;
             maxActiveData = phoneCapability.maxActiveData;
-            maxActiveInternetData = phoneCapability.maxActiveInternetData;
+            // If the maxActiveVoice field has been set, use that value. Otherwise, default to the
+            // legacy behavior and rely on the maxActiveInternetData field:
+            if (phoneCapability.maxActiveVoice ==
+                    android.hardware.radio.config.PhoneCapability.UNKNOWN) {
+                maxActiveVoiceCalls = phoneCapability.maxActiveInternetData;
+            } else {
+                maxActiveVoiceCalls = phoneCapability.maxActiveVoice;
+            }
             validationBeforeSwitchSupported = phoneCapability.isInternetLingeringSupported;
             for (int modemId : phoneCapability.logicalModemIds) {
                 logicalModemList.add(new ModemInfo(modemId));
@@ -4650,16 +4495,15 @@
             final android.hardware.radio.config.V1_1.PhoneCapability phoneCapability =
                     (android.hardware.radio.config.V1_1.PhoneCapability) o;
             maxActiveData = phoneCapability.maxActiveData;
-            maxActiveInternetData = phoneCapability.maxActiveInternetData;
+            // maxActiveInternetData defines how many logical modems can have internet PDN
+            // connections simultaneously. For L+L DSDS modem it’s 1, and for DSDA modem it’s 2.
+            maxActiveVoiceCalls = phoneCapability.maxActiveInternetData;
             validationBeforeSwitchSupported = phoneCapability.isInternetLingeringSupported;
             for (android.hardware.radio.config.V1_1.ModemInfo modemInfo :
                     phoneCapability.logicalModemList) {
                 logicalModemList.add(new ModemInfo(modemInfo.modemId));
             }
         }
-        // maxActiveInternetData defines how many logical modems can have internet PDN connections
-        // simultaneously. For L+L DSDS modem it’s 1, and for DSDA modem it’s 2.
-        maxActiveVoiceCalls = maxActiveInternetData;
         return new PhoneCapability(maxActiveVoiceCalls, maxActiveData, logicalModemList,
                 validationBeforeSwitchSupported, deviceNrCapabilities);
     }
@@ -4702,13 +4546,13 @@
     }
 
     /**
-     * Convert EmergencyRegResult.aidl to EmergencyRegResult.
+     * Convert EmergencyRegResult.aidl to EmergencyRegistrationResult.
      * @param halResult EmergencyRegResult.aidl in HAL.
-     * @return Converted EmergencyRegResult.
+     * @return Converted EmergencyRegistrationResult.
      */
-    public static EmergencyRegResult convertHalEmergencyRegResult(
+    public static EmergencyRegistrationResult convertHalEmergencyRegResult(
             android.hardware.radio.network.EmergencyRegResult halResult) {
-        return new EmergencyRegResult(
+        return new EmergencyRegistrationResult(
                 halResult.accessNetwork,
                 convertHalRegState(halResult.regState),
                 halResult.emcDomain,
@@ -5377,6 +5221,20 @@
                 return "SET_N1_MODE_ENABLED";
             case RIL_REQUEST_IS_N1_MODE_ENABLED:
                 return "IS_N1_MODE_ENABLED";
+            case RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING:
+                return "SET_LOCATION_PRIVACY_SETTING";
+            case RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING:
+                return "GET_LOCATION_PRIVACY_SETTING";
+            case RIL_REQUEST_IS_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED:
+                return "IS_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED";
+            case RIL_REQUEST_SET_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED:
+                return "SET_CELLULAR_IDENTIFIER_DISCLOSED_ENABLED";
+            case RIL_REQUEST_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED:
+                return "SET_SECURITY_ALGORITHMS_UPDATED_ENABLED";
+            case RIL_REQUEST_IS_SECURITY_ALGORITHMS_UPDATED_ENABLED:
+                return "IS_SECURITY_ALGORITHMS_UPDATED_ENABLED";
+            case RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT:
+                return "GET_SIMULTANEOUS_CALLING_SUPPORT";
             default:
                 return "<unknown request " + request + ">";
         }
@@ -5499,6 +5357,10 @@
                 return "UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED";
             case RIL_UNSOL_SLICING_CONFIG_CHANGED:
                 return "UNSOL_SLICING_CONFIG_CHANGED";
+            case RIL_UNSOL_CELLULAR_IDENTIFIER_DISCLOSED:
+                return "UNSOL_CELLULAR_IDENTIFIER_DISCLOSED";
+            case RIL_UNSOL_SECURITY_ALGORITHMS_UPDATED:
+                return "UNSOL_SECURITY_ALGORITHMS_UPDATED";
             /* The follow unsols are not defined in RIL.h */
             case RIL_UNSOL_ICC_SLOT_STATUS:
                 return "UNSOL_ICC_SLOT_STATUS";
@@ -5514,12 +5376,16 @@
                 return "UNSOL_BARRING_INFO_CHANGED";
             case RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT:
                 return "UNSOL_EMERGENCY_NETWORK_SCAN_RESULT";
-            case RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION:
-                return "UNSOL_TRIGGER_IMS_DEREGISTRATION";
             case RIL_UNSOL_CONNECTION_SETUP_FAILURE:
                 return "UNSOL_CONNECTION_SETUP_FAILURE";
             case RIL_UNSOL_NOTIFY_ANBR:
                 return "UNSOL_NOTIFY_ANBR";
+            case RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION:
+                return "UNSOL_TRIGGER_IMS_DEREGISTRATION";
+            case RIL_UNSOL_IMEI_MAPPING_CHANGED:
+                return "UNSOL_IMEI_MAPPING_CHANGED";
+            case RIL_UNSOL_SIMULTANEOUS_CALLING_SUPPORT_CHANGED:
+                return "UNSOL_SIMULTANEOUS_CALLING_SUPPORT_CHANGED";
             default:
                 return "<unknown response " + response + ">";
         }
@@ -5636,7 +5502,7 @@
                 try {
                     val = o.getClass().getDeclaredMethod(getTagMethod).invoke(o);
                 } catch (NoSuchMethodException | IllegalAccessException
-                        | InvocationTargetException e) {
+                         | InvocationTargetException e) {
                     loge(e.toString());
                 }
                 if (val != null) {
@@ -5905,47 +5771,6 @@
     }
 
     /**
-     * Convert satellite-related errors from CommandException.Error to
-     * SatelliteManager.SatelliteServiceResult.
-     * @param error The satellite error.
-     * @return The converted SatelliteServiceResult.
-     */
-    @SatelliteManager.SatelliteError
-    public static int convertToSatelliteError(
-            CommandException.Error error) {
-        switch (error) {
-            case INTERNAL_ERR:
-                //fallthrough to SYSTEM_ERR
-            case MODEM_ERR:
-                //fallthrough to SYSTEM_ERR
-            case SYSTEM_ERR:
-                return SatelliteManager.SATELLITE_MODEM_ERROR;
-            case INVALID_ARGUMENTS:
-                return SatelliteManager.SATELLITE_INVALID_ARGUMENTS;
-            case INVALID_MODEM_STATE:
-                return SatelliteManager.SATELLITE_INVALID_MODEM_STATE;
-            case RADIO_NOT_AVAILABLE:
-                return SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE;
-            case REQUEST_NOT_SUPPORTED:
-                return SatelliteManager.SATELLITE_REQUEST_NOT_SUPPORTED;
-            case NO_MEMORY:
-                //fallthrough to NO_RESOURCES
-            case NO_RESOURCES:
-                return SatelliteManager.SATELLITE_NO_RESOURCES;
-            case NETWORK_ERR:
-                return SatelliteManager.SATELLITE_NETWORK_ERROR;
-            case NO_NETWORK_FOUND:
-                return SatelliteManager.SATELLITE_NOT_REACHABLE;
-            case ABORTED:
-                return SatelliteManager.SATELLITE_REQUEST_ABORTED;
-            case ACCESS_BARRED:
-                return SatelliteManager.SATELLITE_ACCESS_BARRED;
-            default:
-                return SatelliteManager.SATELLITE_ERROR;
-        }
-    }
-
-    /**
      * Converts the call state to HAL IMS call state.
      *
      * @param state The {@link Call.State}.
@@ -5964,6 +5789,34 @@
         }
     }
 
+    /** Convert an AIDL-based CellularIdentifierDisclosure to its Java wrapper. */
+    public static CellularIdentifierDisclosure convertCellularIdentifierDisclosure(
+            android.hardware.radio.network.CellularIdentifierDisclosure identifierDisclsoure) {
+        if (identifierDisclsoure == null) {
+            return null;
+        }
+
+        return new CellularIdentifierDisclosure(
+                identifierDisclsoure.protocolMessage,
+                identifierDisclsoure.identifier,
+                identifierDisclsoure.plmn,
+                identifierDisclsoure.isEmergency);
+    }
+
+    /** Convert an AIDL-based SecurityAlgorithmUpdate to its Java wrapper. */
+    public static SecurityAlgorithmUpdate convertSecurityAlgorithmUpdate(
+            android.hardware.radio.network.SecurityAlgorithmUpdate securityAlgorithmUpdate) {
+        if (securityAlgorithmUpdate == null) {
+            return null;
+        }
+
+        return new SecurityAlgorithmUpdate(
+                securityAlgorithmUpdate.connectionEvent,
+                securityAlgorithmUpdate.encryption,
+                securityAlgorithmUpdate.integrity,
+                securityAlgorithmUpdate.isUnprotectedEmergency);
+    }
+
     private static void logd(String log) {
         Rlog.d("RILUtils", log);
     }
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index 3e2be1d..13f6502 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -23,6 +23,7 @@
 import static com.android.internal.telephony.RILConstants.REQUEST_NOT_SUPPORTED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PHONE_CAPABILITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SLOT_STATUS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_DATA_MODEM;
@@ -61,7 +62,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_0 = new HalVersion(1, 0);
     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);
@@ -80,6 +80,8 @@
 
     protected Registrant mSimSlotStatusRegistrant;
 
+    protected Registrant mSimultaneousCallingSupportStatusRegistrant;
+
     private boolean isMobileDataCapable(Context context) {
         final TelephonyManager tm = context.getSystemService(TelephonyManager.class);
         return tm != null && tm.isDataCapable();
@@ -317,13 +319,7 @@
         }
 
         if (mRadioConfigProxy.isEmpty()) {
-            try {
-                mRadioConfigProxy.setHidl(RADIO_CONFIG_HAL_VERSION_1_0,
-                        android.hardware.radio.config.V1_0.IRadioConfig.getService(true));
-            } catch (RemoteException | NoSuchElementException e) {
-                mRadioConfigProxy.clear();
-                loge("getHidlRadioConfigProxy1_0: RadioConfigProxy getService | linkToDeath: " + e);
-            }
+            loge("IRadioConfig <1.1 is no longer supported.");
         }
 
         if (!mRadioConfigProxy.isEmpty()) {
@@ -485,6 +481,34 @@
     }
 
     /**
+     * Wrapper function for IRadioConfig.getSimultaneousCallingSupport().
+     */
+    public void updateSimultaneousCallingSupport(Message result) {
+        RadioConfigProxy proxy = getRadioConfigProxy(null);
+        if (proxy.isEmpty()) return;
+
+        if (proxy.getVersion().less(RIL.RADIO_HAL_VERSION_2_2)) {
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT, result,
+                mDefaultWorkSource);
+        if (DBG) {
+            logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+        try {
+            proxy.updateSimultaneousCallingSupport(rr.mSerial);
+        } catch (RemoteException | RuntimeException e) {
+            resetProxyAndRequestList("updateSimultaneousCallingSupport", e);
+        }
+    }
+
+    /**
      * Wrapper function for IRadioConfig.getPhoneCapability().
      */
     public void getPhoneCapability(Message result) {
@@ -573,6 +597,14 @@
     }
 
     /**
+     * Register a handler to get SIM slots that support simultaneous calling changed notifications.
+     */
+    public void registerForSimultaneousCallingSupportStatusChanged(Handler h, int what,
+            Object obj) {
+        mSimultaneousCallingSupportStatusRegistrant = new Registrant(h, what, obj);
+    }
+
+    /**
      * Register a handler to get SIM slot status changed notifications.
      */
     public void registerForSimSlotStatusChanged(Handler h, int what, Object obj) {
diff --git a/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java b/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
index 76e6d5e..9aa1aaa 100644
--- a/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
+++ b/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
@@ -17,12 +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;
 
 /**
  * This class is the AIDL implementation of IRadioConfigIndication interface.
@@ -51,6 +53,19 @@
         }
     }
 
+    /**
+     * Indication that the logical slots that support simultaneous calling has changed.
+     */
+    @Override
+    public void onSimultaneousCallingSupportChanged(int[] enabledLogicalSlots) {
+        ArrayList<Integer> ret = RILUtils.primitiveArrayToArrayList(enabledLogicalSlots);
+        logd("onSimultaneousCallingSupportChanged: enabledLogicalSlots = " + ret);
+        if (mRadioConfig.mSimultaneousCallingSupportStatusRegistrant != null) {
+            mRadioConfig.mSimultaneousCallingSupportStatusRegistrant.notifyRegistrant(
+                    new AsyncResult(null, ret, null));
+        }
+    }
+
     private static void logd(String log) {
         Rlog.d(TAG, "[UNSL]< " + log);
         Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", log);
diff --git a/src/java/com/android/internal/telephony/RadioConfigProxy.java b/src/java/com/android/internal/telephony/RadioConfigProxy.java
index edeb558..b6c6d68 100644
--- a/src/java/com/android/internal/telephony/RadioConfigProxy.java
+++ b/src/java/com/android/internal/telephony/RadioConfigProxy.java
@@ -35,7 +35,7 @@
     private final RadioConfigHidlServiceDeathRecipient mRadioConfigHidlServiceDeathRecipient;
     private final RadioConfigAidlServiceDeathRecipient mRadioConfigAidlServiceDeathRecipient;
 
-    private volatile android.hardware.radio.config.V1_0.IRadioConfig mHidlRadioConfigProxy = null;
+    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;
@@ -57,7 +57,7 @@
      */
     public void setHidl(
             HalVersion radioConfigHalVersion,
-            android.hardware.radio.config.V1_0.IRadioConfig radioConfig) {
+            android.hardware.radio.config.V1_1.IRadioConfig radioConfig) {
         mRadioConfigHalVersion = radioConfigHalVersion;
         mHidlRadioConfigProxy = radioConfig;
         mIsAidl = false;
@@ -65,19 +65,11 @@
     }
 
     /**
-     * Get HIDL IRadioConfig V1_0
-     * @return IRadioConfigV1_0
-     */
-    public android.hardware.radio.config.V1_0.IRadioConfig getHidl10() {
-        return mHidlRadioConfigProxy;
-    }
-
-    /**
      * Get HIDL IRadioConfig V1_1
      * @return IRadioConfigV1_1
      */
     public android.hardware.radio.config.V1_1.IRadioConfig getHidl11() {
-        return (android.hardware.radio.config.V1_1.IRadioConfig) mHidlRadioConfigProxy;
+        return mHidlRadioConfigProxy;
     }
 
     /**
@@ -192,7 +184,7 @@
         if (isAidl()) {
             getAidl().getSimSlotsStatus(serial);
         } else {
-            getHidl10().getSimSlotsStatus(serial);
+            getHidl11().getSimSlotsStatus(serial);
         }
     }
 
@@ -227,12 +219,22 @@
         if (isAidl()) {
             getAidl().setSimSlotsMapping(serial, RILUtils.convertSimSlotsMapping(slotMapping));
         } else {
-            getHidl10().setSimSlotsMapping(serial,
+            getHidl11().setSimSlotsMapping(serial,
                     RILUtils.convertSlotMappingToList(slotMapping));
         }
     }
 
     /**
+     * Wrapper function for IRadioConfig.getSimultaneousCallingSupport()
+     */
+    public void updateSimultaneousCallingSupport(int serial) throws RemoteException {
+        if (isAidl()) {
+            getAidl().getSimultaneousCallingSupport(serial);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
      * Wrapper function for using IRadioConfig.setNumOfLiveModems(int32_t serial,
      * byte numOfLiveModems) to switch between single-sim and multi-sim.
      */
@@ -265,13 +267,13 @@
         private static final String TAG = "RadioConfigHidlSDR";
 
         private final RadioConfig mRadioConfig;
-        private android.hardware.radio.config.V1_0.IRadioConfig mService;
+        private android.hardware.radio.config.V1_1.IRadioConfig mService;
 
         RadioConfigHidlServiceDeathRecipient(RadioConfig radioConfig) {
             mRadioConfig = radioConfig;
         }
 
-        public void setService(android.hardware.radio.config.V1_0.IRadioConfig service) {
+        public void setService(android.hardware.radio.config.V1_1.IRadioConfig service) {
             mService = service;
         }
 
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java b/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java
index 2049654..0a41b11 100644
--- a/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java
+++ b/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.hardware.radio.RadioResponseInfo;
 import android.os.RemoteException;
 import android.telephony.PhoneCapability;
 
@@ -23,6 +24,7 @@
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Set;
 
 /**
@@ -112,6 +114,30 @@
     }
 
     /**
+     * Response function for IRadioConfig.getSimultaneousCallingSupport().
+     */
+    @Override
+    public void getSimultaneousCallingSupportResponse(
+            android.hardware.radio.RadioResponseInfo info,
+            int[] enabledLogicalSlots)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            ArrayList<Integer> ret = RILUtils.primitiveArrayToArrayList(enabledLogicalSlots);
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                logd(rr, RILUtils.requestToString(rr.mRequest) + " " + ret);
+            } else {
+                rr.onError(info.error, enabledLogicalSlots);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getSimultaneousCallingSupportResponse: Error " + info.toString());
+        }
+    }
+
+    /**
      * Response function for IRadioConfig.getSimSlotsStatus().
      */
     @Override
diff --git a/src/java/com/android/internal/telephony/RadioDataProxy.java b/src/java/com/android/internal/telephony/RadioDataProxy.java
index 9671077..40db9e5 100644
--- a/src/java/com/android/internal/telephony/RadioDataProxy.java
+++ b/src/java/com/android/internal/telephony/RadioDataProxy.java
@@ -22,9 +22,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.Rlog;
-import android.telephony.ServiceState;
 import android.telephony.data.DataProfile;
-import android.telephony.data.DataService;
 import android.telephony.data.NetworkSliceInfo;
 import android.telephony.data.TrafficDescriptor;
 
@@ -34,8 +32,8 @@
 import java.util.ArrayList;
 
 /**
- * A holder for IRadioData. Use getHidl to get IRadio 1.0 and call the HIDL implementations or
- * getAidl to get IRadioData and call the AIDL implementations of the HAL APIs.
+ * A holder for IRadioData.
+ * Use getAidl to get IRadioData and call the AIDL implementations of the HAL APIs.
  */
 public class RadioDataProxy extends RadioServiceProxy {
     private static final String TAG = "RadioDataProxy";
@@ -129,12 +127,8 @@
         if (isEmpty()) return;
         if (isAidl()) {
             mDataProxy.deactivateDataCall(serial, cid, reason);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_2)) {
-            ((android.hardware.radio.V1_2.IRadio) mRadioProxy).deactivateDataCall_1_2(
-                    serial, cid, reason);
         } else {
-            mRadioProxy.deactivateDataCall(serial, cid,
-                    reason == DataService.REQUEST_REASON_SHUTDOWN);
+            mRadioProxy.deactivateDataCall_1_2(serial, cid, reason);
         }
     }
 
@@ -216,11 +210,9 @@
      * Call IRadioData#setDataProfile
      * @param serial Serial number of request
      * @param profiles Array of DataProfiles to set
-     * @param isRoaming Whether or not the device is roaming
      * @throws RemoteException
      */
-    public void setDataProfile(int serial, DataProfile[] profiles, boolean isRoaming)
-            throws RemoteException {
+    public void setDataProfile(int serial, DataProfile[] profiles) throws RemoteException {
         if (isEmpty()) return;
         if (isAidl()) {
             android.hardware.radio.data.DataProfileInfo[] dpis =
@@ -235,22 +227,12 @@
                 dpis.add(RILUtils.convertToHalDataProfile15(dp));
             }
             ((android.hardware.radio.V1_5.IRadio) mRadioProxy).setDataProfile_1_5(serial, dpis);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
+        } else {
             ArrayList<android.hardware.radio.V1_4.DataProfileInfo> dpis = new ArrayList<>();
             for (DataProfile dp : profiles) {
                 dpis.add(RILUtils.convertToHalDataProfile14(dp));
             }
-            ((android.hardware.radio.V1_4.IRadio) mRadioProxy).setDataProfile_1_4(serial, dpis);
-        } else {
-            ArrayList<android.hardware.radio.V1_0.DataProfileInfo> dpis = new ArrayList<>();
-            for (DataProfile dp : profiles) {
-                if (dp.isPersistent()) {
-                    dpis.add(RILUtils.convertToHalDataProfile10(dp));
-                }
-            }
-            if (!dpis.isEmpty()) {
-                mRadioProxy.setDataProfile(serial, dpis, isRoaming);
-            }
+            mRadioProxy.setDataProfile_1_4(serial, dpis);
         }
     }
 
@@ -277,10 +259,9 @@
      * Call IRadioData#setInitialAttachApn
      * @param serial Serial number of request
      * @param dataProfile Data profile containing APN settings
-     * @param isRoaming Whether or not the device is roaming
      * @throws RemoteException
      */
-    public void setInitialAttachApn(int serial, DataProfile dataProfile, boolean isRoaming)
+    public void setInitialAttachApn(int serial, DataProfile dataProfile)
             throws RemoteException {
         if (isEmpty()) return;
         if (isAidl()) {
@@ -288,22 +269,17 @@
         } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
             ((android.hardware.radio.V1_5.IRadio) mRadioProxy).setInitialAttachApn_1_5(serial,
                     RILUtils.convertToHalDataProfile15(dataProfile));
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
-            ((android.hardware.radio.V1_4.IRadio) mRadioProxy).setInitialAttachApn_1_4(serial,
-                    RILUtils.convertToHalDataProfile14(dataProfile));
         } else {
-            mRadioProxy.setInitialAttachApn(serial, RILUtils.convertToHalDataProfile10(dataProfile),
-                    dataProfile.isPersistent(), isRoaming);
+            mRadioProxy.setInitialAttachApn_1_4(serial,
+                    RILUtils.convertToHalDataProfile14(dataProfile));
         }
     }
 
     /**
      * Call IRadioData#setupDataCall
      * @param serial Serial number of request
-     * @param phoneId Phone ID of the requestor
      * @param accessNetwork Access network to setup the data call
      * @param dataProfileInfo Data profile info
-     * @param isRoaming Whether or not the device is roaming
      * @param roamingAllowed Whether or not data roaming is allowed by the user
      * @param reason Request reason
      * @param linkProperties LinkProperties containing address and DNS info
@@ -316,15 +292,14 @@
      *                            is allowed
      * @throws RemoteException
      */
-    public void setupDataCall(int serial, int phoneId, int accessNetwork,
-            DataProfile dataProfileInfo, boolean isRoaming, boolean roamingAllowed, int reason,
-            LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
-            TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed)
-            throws RemoteException {
+    public void setupDataCall(int serial, int accessNetwork, DataProfile dataProfileInfo,
+            boolean roamingAllowed, int reason, LinkProperties linkProperties, int pduSessionId,
+            NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
+            boolean matchAllRuleAllowed) throws RemoteException {
         if (isEmpty()) return;
         ArrayList<String> addresses = new ArrayList<>();
         ArrayList<String> dnses = new ArrayList<>();
-        String[] dnsesArr = null;
+        String[] dnsesArr;
         if (linkProperties != null) {
             for (InetAddress address : linkProperties.getAddresses()) {
                 addresses.add(address.getHostAddress());
@@ -361,31 +336,10 @@
                     accessNetwork, RILUtils.convertToHalDataProfile15(dataProfileInfo),
                     roamingAllowed, reason, RILUtils.convertToHalLinkProperties15(linkProperties),
                     dnses);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
-            ((android.hardware.radio.V1_4.IRadio) mRadioProxy).setupDataCall_1_4(serial,
-                    accessNetwork, RILUtils.convertToHalDataProfile14(dataProfileInfo),
-                    roamingAllowed, reason, addresses, dnses);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_2)) {
-            ((android.hardware.radio.V1_2.IRadio) mRadioProxy).setupDataCall_1_2(serial,
-                    accessNetwork, RILUtils.convertToHalDataProfile10(dataProfileInfo),
-                    dataProfileInfo.isPersistent(), roamingAllowed, isRoaming, reason, addresses,
-                    dnses);
         } else {
-            // Getting data RAT here is just a workaround to support the older 1.0 vendor RIL.
-            // The new data service interface passes access network type instead of RAT for
-            // setup data request. It is impossible to convert access network type back to RAT here,
-            // so we directly get the data RAT from phone.
-            int dataRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
-            Phone phone = PhoneFactory.getPhone(phoneId);
-            if (phone != null) {
-                ServiceState ss = phone.getServiceState();
-                if (ss != null) {
-                    dataRat = ss.getRilDataRadioTechnology();
-                }
-            }
-            mRadioProxy.setupDataCall(serial, dataRat,
-                    RILUtils.convertToHalDataProfile10(dataProfileInfo),
-                    dataProfileInfo.isPersistent(), roamingAllowed, isRoaming);
+            mRadioProxy.setupDataCall_1_4(serial, accessNetwork,
+                    RILUtils.convertToHalDataProfile14(dataProfileInfo),
+                    roamingAllowed, reason, addresses, dnses);
         }
     }
 
@@ -415,7 +369,7 @@
      */
     public void startKeepalive(int serial, int contextId, KeepalivePacketData packetData,
             int intervalMillis, Message result) throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_1)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             android.hardware.radio.data.KeepaliveRequest req =
                     new android.hardware.radio.data.KeepaliveRequest();
@@ -476,7 +430,7 @@
             req.destinationPort = packetData.getDstPort();
             req.maxKeepaliveIntervalMillis = intervalMillis;
 
-            ((android.hardware.radio.V1_1.IRadio) mRadioProxy).startKeepalive(serial, req);
+            mRadioProxy.startKeepalive(serial, req);
         }
     }
 
@@ -487,11 +441,11 @@
      * @throws RemoteException
      */
     public void stopKeepalive(int serial, int sessionHandle) throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_1)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             mDataProxy.stopKeepalive(serial, sessionHandle);
         } else {
-            ((android.hardware.radio.V1_1.IRadio) mRadioProxy).stopKeepalive(serial, sessionHandle);
+            mRadioProxy.stopKeepalive(serial, sessionHandle);
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index 4f75412..aadfe62 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -240,28 +240,18 @@
     }
 
     public void currentSignalStrength(int indicationType,
-                                      android.hardware.radio.V1_0.SignalStrength signalStrength) {
-        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
-
-        SignalStrength ssInitial = RILUtils.convertHalSignalStrength(signalStrength);
-
-        SignalStrength ss = mRil.fixupSignalStrength10(ssInitial);
-        // Note this is set to "verbose" because it happens frequently
-        if (mRil.isLogvOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
-
-        if (mRil.mSignalStrengthRegistrant != null) {
-            mRil.mSignalStrengthRegistrant.notifyRegistrant(new AsyncResult (null, ss, null));
-        }
+            android.hardware.radio.V1_0.SignalStrength signalStrength) {
+        mRil.unsljLogMore(RIL_UNSOL_SIGNAL_STRENGTH, "unsupported on IRadio < 1.4");
     }
 
     /**
      * Indicates current link capacity estimate.
      */
     public void currentLinkCapacityEstimate(int indicationType,
-                                            android.hardware.radio.V1_2.LinkCapacityEstimate lce) {
+            android.hardware.radio.V1_2.LinkCapacityEstimate lce) {
         mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
+        List<LinkCapacityEstimate> response = RILUtils.convertHalLinkCapacityEstimate(lce);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
@@ -277,7 +267,7 @@
             android.hardware.radio.V1_6.LinkCapacityEstimate lce) {
         mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
+        List<LinkCapacityEstimate> response = RILUtils.convertHalLinkCapacityEstimate(lce);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
@@ -290,16 +280,8 @@
      * Indicates the current signal strength of the camped or primary serving cell.
      */
     public void currentSignalStrength_1_2(int indicationType,
-                                      android.hardware.radio.V1_2.SignalStrength signalStrength) {
-        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
-
-        SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength);
-        // Note this is set to "verbose" because it happens frequently
-        if (mRil.isLogvOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
-
-        if (mRil.mSignalStrengthRegistrant != null) {
-            mRil.mSignalStrengthRegistrant.notifyRegistrant(new AsyncResult(null, ss, null));
-        }
+            android.hardware.radio.V1_2.SignalStrength signalStrength) {
+        mRil.unsljLogMore(RIL_UNSOL_SIGNAL_STRENGTH, "unsupported on IRadio < 1.4");
     }
 
     /**
@@ -359,8 +341,7 @@
      */
     public void currentPhysicalChannelConfigs(int indicationType,
             ArrayList<android.hardware.radio.V1_2.PhysicalChannelConfig> configs) {
-        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
-        physicalChannelConfigsIndication(configs);
+        mRil.unsljLogMore(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, "unsupported on IRadio < 1.4");
     }
 
     /**
@@ -393,7 +374,7 @@
     /** Indicates current data call list. */
     public void dataCallListChanged(int indicationType,
             ArrayList<android.hardware.radio.V1_0.SetupDataCallResult> dcList) {
-        responseDataCallListChanged(indicationType, dcList);
+        mRil.unsljLogMore(RIL_UNSOL_DATA_CALL_LIST_CHANGED, "unsupported on IRadio < 1.4");
     }
 
     /** Indicates current data call list with radio HAL 1.4. */
@@ -803,15 +784,13 @@
     /** Get unsolicited message for cellInfoList */
     public void cellInfoList(int indicationType,
             ArrayList<android.hardware.radio.V1_0.CellInfo> records) {
-        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
-        responseCellInfoList(records);
+        mRil.unsljLogMore(RIL_UNSOL_CELL_INFO_LIST, "unsupported on IRadio < 1.4");
     }
 
     /** Get unsolicited message for cellInfoList using HAL V1_2 */
     public void cellInfoList_1_2(int indicationType,
             ArrayList<android.hardware.radio.V1_2.CellInfo> records) {
-        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
-        responseCellInfoList(records);
+        mRil.unsljLogMore(RIL_UNSOL_CELL_INFO_LIST, "unsupported on IRadio < 1.4");
     }
 
     /** Get unsolicited message for cellInfoList using HAL V1_4 */
@@ -854,20 +833,20 @@
 
     /** Incremental network scan results */
     public void networkScanResult(int indicationType,
-                                  android.hardware.radio.V1_1.NetworkScanResult result) {
-        responseNetworkScan(indicationType, result);
+            android.hardware.radio.V1_1.NetworkScanResult result) {
+        mRil.unsljLogMore(RIL_UNSOL_NETWORK_SCAN_RESULT, "unsupported on IRadio < 1.4");
     }
 
     /** Incremental network scan results with HAL V1_2 */
     public void networkScanResult_1_2(int indicationType,
-                                      android.hardware.radio.V1_2.NetworkScanResult result) {
-        responseNetworkScan_1_2(indicationType, result);
+            android.hardware.radio.V1_2.NetworkScanResult result) {
+        mRil.unsljLogMore(RIL_UNSOL_NETWORK_SCAN_RESULT, "unsupported on IRadio < 1.4");
     }
 
     /** Incremental network scan results with HAL V1_4 */
     public void networkScanResult_1_4(int indicationType,
-                                      android.hardware.radio.V1_4.NetworkScanResult result) {
-        responseNetworkScan_1_4(indicationType, result);
+            android.hardware.radio.V1_4.NetworkScanResult result) {
+        responseNetworkScan(indicationType, result);
     }
 
     /** Incremental network scan results with HAL V1_5 */
@@ -918,8 +897,7 @@
                 new AsyncResult (null, response, null));
     }
 
-    public void hardwareConfigChanged(
-            int indicationType,
+    public void hardwareConfigChanged(int indicationType,
             ArrayList<android.hardware.radio.V1_0.HardwareConfig> configs) {
         mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
@@ -932,7 +910,7 @@
     }
 
     public void radioCapabilityIndication(int indicationType,
-                                          android.hardware.radio.V1_0.RadioCapability rc) {
+            android.hardware.radio.V1_0.RadioCapability rc) {
         mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         RadioCapability response = RILUtils.convertHalRadioCapability(rc, mRil);
@@ -1002,15 +980,7 @@
     }
 
     public void lceData(int indicationType, LceDataInfo lce) {
-        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
-
-        List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
-
-        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
-
-        if (mRil.mLceInfoRegistrants != null) {
-            mRil.mLceInfoRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
-        }
+        mRil.unsljLogMore(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, "unsupported on IRadio < 1.4");
     }
 
     public void pcoData(int indicationType, PcoDataInfo pco) {
@@ -1198,16 +1168,7 @@
         List<PhysicalChannelConfig> response = new ArrayList<>(configs.size());
         try {
             for (Object obj : configs) {
-                if (obj instanceof android.hardware.radio.V1_2.PhysicalChannelConfig) {
-                    android.hardware.radio.V1_2.PhysicalChannelConfig config =
-                            (android.hardware.radio.V1_2.PhysicalChannelConfig) obj;
-
-                    response.add(new PhysicalChannelConfig.Builder()
-                            .setCellConnectionStatus(RILUtils.convertHalCellConnectionStatus(
-                                    config.status))
-                            .setCellBandwidthDownlinkKhz(config.cellBandwidthDownlink)
-                            .build());
-                } else if (obj instanceof android.hardware.radio.V1_4.PhysicalChannelConfig) {
+                if (obj instanceof android.hardware.radio.V1_4.PhysicalChannelConfig) {
                     android.hardware.radio.V1_4.PhysicalChannelConfig config =
                             (android.hardware.radio.V1_4.PhysicalChannelConfig) obj;
                     PhysicalChannelConfig.Builder builder = new PhysicalChannelConfig.Builder();
@@ -1243,7 +1204,11 @@
                     }
                     if (band == PhysicalChannelConfig.BAND_UNKNOWN) {
                         mRil.riljLoge("Unsupported unknown band.");
-                        return;
+                        // TODO, b/288310456,
+                        //  If the band is unknown, PhysicalChannelConfig can be built without
+                        //  setBand. It should be enforced not to allow "unknown" bands in the
+                        //  near future.
+                        // return;
                     } else {
                         builder.setBand(band);
                     }
@@ -1277,28 +1242,6 @@
     }
 
     private void responseNetworkScan(int indicationType,
-            android.hardware.radio.V1_1.NetworkScanResult result) {
-        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
-
-        ArrayList<CellInfo> cellInfos =
-                RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos));
-        NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
-        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
-        mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
-    }
-
-    private void responseNetworkScan_1_2(int indicationType,
-            android.hardware.radio.V1_2.NetworkScanResult result) {
-        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
-
-        ArrayList<CellInfo> cellInfos =
-                RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos));
-        NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
-        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
-        mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
-    }
-
-    private void responseNetworkScan_1_4(int indicationType,
             android.hardware.radio.V1_4.NetworkScanResult result) {
         mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
diff --git a/src/java/com/android/internal/telephony/RadioMessagingProxy.java b/src/java/com/android/internal/telephony/RadioMessagingProxy.java
index 69ccf36..c652284 100644
--- a/src/java/com/android/internal/telephony/RadioMessagingProxy.java
+++ b/src/java/com/android/internal/telephony/RadioMessagingProxy.java
@@ -25,8 +25,8 @@
 import java.util.ArrayList;
 
 /**
- * A holder for IRadioMessaging. Use getHidl to get IRadio 1.0 and call the HIDL implementations or
- * getAidl to get IRadioMessaging and call the AIDL implementations of the HAL APIs.
+ * A holder for IRadioMessaging.
+ * Use getAidl to get IRadioMessaging and call the AIDL implementations of the HAL APIs.
  */
 public class RadioMessagingProxy extends RadioServiceProxy {
     private static final String TAG = "RadioMessagingProxy";
diff --git a/src/java/com/android/internal/telephony/RadioModemProxy.java b/src/java/com/android/internal/telephony/RadioModemProxy.java
index 4178293..cdcbcc0 100644
--- a/src/java/com/android/internal/telephony/RadioModemProxy.java
+++ b/src/java/com/android/internal/telephony/RadioModemProxy.java
@@ -20,8 +20,8 @@
 import android.telephony.Rlog;
 
 /**
- * A holder for IRadioModem. Use getHidl to get IRadio 1.0 and call the HIDL implementations or
- * getAidl to get IRadioModem and call the AIDL implementations of the HAL APIs.
+ * A holder for IRadioModem.
+ * Use getAidl to get IRadioModem and call the AIDL implementations of the HAL APIs.
  */
 public class RadioModemProxy extends RadioServiceProxy {
     private static final String TAG = "RadioModemProxy";
@@ -83,11 +83,11 @@
      * @throws RemoteException
      */
     public void enableModem(int serial, boolean on) throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_3)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             mModemProxy.enableModem(serial, on);
         } else {
-            ((android.hardware.radio.V1_3.IRadio) mRadioProxy).enableModem(serial, on);
+            mRadioProxy.enableModem(serial, on);
         }
     }
 
@@ -166,11 +166,11 @@
      * @throws RemoteException
      */
     public void getModemStackStatus(int serial) throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_3)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             mModemProxy.getModemStackStatus(serial);
         } else {
-            ((android.hardware.radio.V1_3.IRadio) mRadioProxy).getModemStackStatus(serial);
+            mRadioProxy.getModemStackStatus(serial);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/RadioNetworkProxy.java b/src/java/com/android/internal/telephony/RadioNetworkProxy.java
index 246c2e0..4acc71a 100644
--- a/src/java/com/android/internal/telephony/RadioNetworkProxy.java
+++ b/src/java/com/android/internal/telephony/RadioNetworkProxy.java
@@ -33,19 +33,17 @@
 import java.util.stream.Collectors;
 
 /**
- * A holder for IRadioNetwork. Use getHidl to get IRadio 1.0 and call the HIDL implementations or
- * getAidl to get IRadioNetwork and call the AIDL implementations of the HAL APIs.
+ * A holder for IRadioNetwork.
+ * Use getAidl to get IRadioNetwork and call the AIDL implementations of the HAL APIs.
  */
 public class RadioNetworkProxy extends RadioServiceProxy {
     private static final String TAG = "RadioNetworkProxy";
     private volatile android.hardware.radio.network.IRadioNetwork mNetworkProxy = null;
 
-    private static final int INDICATION_FILTERS_ALL_V1_0 =
+    private static final int INDICATION_FILTERS_ALL_V1_2 =
             android.hardware.radio.V1_5.IndicationFilter.SIGNAL_STRENGTH
                     | android.hardware.radio.V1_5.IndicationFilter.FULL_NETWORK_STATE
-                    | android.hardware.radio.V1_5.IndicationFilter.DATA_CALL_DORMANCY_CHANGED;
-    private static final int INDICATION_FILTERS_ALL_V1_2 =
-            INDICATION_FILTERS_ALL_V1_0
+                    | android.hardware.radio.V1_5.IndicationFilter.DATA_CALL_DORMANCY_CHANGED
                     | android.hardware.radio.V1_5.IndicationFilter.LINK_CAPACITY_ESTIMATE
                     | android.hardware.radio.V1_5.IndicationFilter.PHYSICAL_CHANNEL_CONFIG;
     private static final int INDICATION_FILTERS_ALL_V1_5 =
@@ -121,11 +119,8 @@
             mNetworkProxy.getAllowedNetworkTypesBitmap(serial);
         } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)) {
             ((android.hardware.radio.V1_6.IRadio) mRadioProxy).getAllowedNetworkTypesBitmap(serial);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
-            ((android.hardware.radio.V1_4.IRadio) mRadioProxy)
-                    .getPreferredNetworkTypeBitmap(serial);
         } else {
-            mRadioProxy.getPreferredNetworkType(serial);
+            mRadioProxy.getPreferredNetworkTypeBitmap(serial);
         }
     }
 
@@ -278,10 +273,8 @@
             mNetworkProxy.getSignalStrength(serial);
         } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)) {
             ((android.hardware.radio.V1_6.IRadio) mRadioProxy).getSignalStrength_1_6(serial);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
-            ((android.hardware.radio.V1_4.IRadio) mRadioProxy).getSignalStrength_1_4(serial);
         } else {
-            mRadioProxy.getSignalStrength(serial);
+            mRadioProxy.getSignalStrength_1_4(serial);
         }
     }
 
@@ -394,12 +387,8 @@
     public void setPreferredNetworkTypeBitmap(int serial, int networkTypesBitmask)
             throws RemoteException {
         if (isEmpty() || mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)) return;
-        if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
-            ((android.hardware.radio.V1_4.IRadio) mRadioProxy).setPreferredNetworkTypeBitmap(serial,
-                    RILUtils.convertToHalRadioAccessFamily(networkTypesBitmask));
-        } else {
-            mRadioProxy.setPreferredNetworkType(serial, networkTypesBitmask);
-        }
+        mRadioProxy.setPreferredNetworkTypeBitmap(serial,
+                RILUtils.convertToHalRadioAccessFamily(networkTypesBitmask));
     }
 
     /**
@@ -478,11 +467,8 @@
         } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
             ((android.hardware.radio.V1_5.IRadio) mRadioProxy).setIndicationFilter_1_5(serial,
                     filter & INDICATION_FILTERS_ALL_V1_5);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_2)) {
-            ((android.hardware.radio.V1_2.IRadio) mRadioProxy).setIndicationFilter_1_2(serial,
-                    filter & INDICATION_FILTERS_ALL_V1_2);
         } else {
-            mRadioProxy.setIndicationFilter(serial, filter & INDICATION_FILTERS_ALL_V1_0);
+            mRadioProxy.setIndicationFilter_1_2(serial, filter & INDICATION_FILTERS_ALL_V1_2);
         }
     }
 
@@ -504,7 +490,7 @@
     public void setLinkCapacityReportingCriteria(int serial, int hysteresisMs, int hysteresisDlKbps,
             int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran)
             throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_2)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             mNetworkProxy.setLinkCapacityReportingCriteria(serial, hysteresisMs, hysteresisDlKbps,
                     hysteresisUlKbps, thresholdsDlKbps, thresholdsUlKbps,
@@ -519,7 +505,7 @@
             if (ran == AccessNetworkConstants.AccessNetworkType.NGRAN) {
                 throw new RuntimeException("NGRAN unsupported on IRadio version: " + mHalVersion);
             }
-            ((android.hardware.radio.V1_2.IRadio) mRadioProxy).setLinkCapacityReportingCriteria(
+            mRadioProxy.setLinkCapacityReportingCriteria(
                     serial, hysteresisMs, hysteresisDlKbps, hysteresisUlKbps,
                     RILUtils.primitiveArrayToArrayList(thresholdsDlKbps),
                     RILUtils.primitiveArrayToArrayList(thresholdsUlKbps),
@@ -603,7 +589,7 @@
      */
     public void setSignalStrengthReportingCriteria(int serial,
             @NonNull List<SignalThresholdInfo> signalThresholdInfos) throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_2)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             android.hardware.radio.network.SignalThresholdInfo[] halSignalThresholdsInfos =
             new android.hardware.radio.network.SignalThresholdInfo[signalThresholdInfos.size()];
@@ -622,14 +608,12 @@
             }
         } else {
             for (SignalThresholdInfo signalThresholdInfo : signalThresholdInfos) {
-                ((android.hardware.radio.V1_2.IRadio) mRadioProxy)
-                        .setSignalStrengthReportingCriteria(serial,
-                                signalThresholdInfo.getHysteresisMs(),
-                                signalThresholdInfo.getHysteresisDb(),
-                                RILUtils.primitiveArrayToArrayList(
-                                        signalThresholdInfo.getThresholds()),
-                                RILUtils.convertToHalAccessNetwork(
-                                        signalThresholdInfo.getRadioAccessNetworkType()));
+                mRadioProxy.setSignalStrengthReportingCriteria(serial,
+                        signalThresholdInfo.getHysteresisMs(),
+                        signalThresholdInfo.getHysteresisDb(),
+                        RILUtils.primitiveArrayToArrayList(signalThresholdInfo.getThresholds()),
+                        RILUtils.convertToHalAccessNetwork(
+                                signalThresholdInfo.getRadioAccessNetworkType()));
             }
         }
     }
@@ -657,7 +641,7 @@
      */
     public void setSystemSelectionChannels(int serial, List<RadioAccessSpecifier> specifiers)
             throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_3)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             mNetworkProxy.setSystemSelectionChannels(serial, !specifiers.isEmpty(),
                     specifiers.stream().map(RILUtils::convertToHalRadioAccessSpecifierAidl)
@@ -668,8 +652,8 @@
                             .map(RILUtils::convertToHalRadioAccessSpecifier15)
                             .collect(Collectors.toCollection(ArrayList::new)));
         } else {
-            ((android.hardware.radio.V1_3.IRadio) mRadioProxy).setSystemSelectionChannels(
-                    serial, !specifiers.isEmpty(), specifiers.stream()
+            mRadioProxy.setSystemSelectionChannels(serial, !specifiers.isEmpty(),
+                    specifiers.stream()
                             .map(RILUtils::convertToHalRadioAccessSpecifier11)
                             .collect(Collectors.toCollection(ArrayList::new)));
         }
@@ -684,7 +668,7 @@
      */
     public void startNetworkScan(int serial, NetworkScanRequest request,
             HalVersion overrideHalVersion, Message result) throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_1)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             android.hardware.radio.network.NetworkScanRequest halRequest =
                     new android.hardware.radio.network.NetworkScanRequest();
@@ -734,7 +718,7 @@
             }
             ((android.hardware.radio.V1_5.IRadio) mRadioProxy).startNetworkScan_1_5(
                     serial, halRequest);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_2)) {
+        } else {
             android.hardware.radio.V1_2.NetworkScanRequest halRequest =
                     new android.hardware.radio.V1_2.NetworkScanRequest();
             halRequest.type = request.getScanType();
@@ -755,31 +739,7 @@
                 }
                 halRequest.specifiers.add(rasInHalFormat);
             }
-
-            if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
-                ((android.hardware.radio.V1_4.IRadio) mRadioProxy).startNetworkScan_1_4(
-                        serial, halRequest);
-            } else {
-                ((android.hardware.radio.V1_2.IRadio) mRadioProxy).startNetworkScan_1_2(
-                        serial, halRequest);
-            }
-        } else {
-            android.hardware.radio.V1_1.NetworkScanRequest halRequest =
-                    new android.hardware.radio.V1_1.NetworkScanRequest();
-            halRequest.type = request.getScanType();
-            halRequest.interval = request.getSearchPeriodicity();
-            for (RadioAccessSpecifier ras : request.getSpecifiers()) {
-                android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
-                        RILUtils.convertToHalRadioAccessSpecifier11(ras);
-                if (rasInHalFormat == null) {
-                    AsyncResult.forMessage(result, null,
-                            CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                    result.sendToTarget();
-                    return;
-                }
-                halRequest.specifiers.add(rasInHalFormat);
-            }
-            ((android.hardware.radio.V1_1.IRadio) mRadioProxy).startNetworkScan(serial, halRequest);
+            mRadioProxy.startNetworkScan_1_4(serial, halRequest);
         }
     }
 
@@ -789,11 +749,11 @@
      * @throws RemoteException
      */
     public void stopNetworkScan(int serial) throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_1)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             mNetworkProxy.stopNetworkScan(serial);
         } else {
-            ((android.hardware.radio.V1_1.IRadio) mRadioProxy).stopNetworkScan(serial);
+            mRadioProxy.stopNetworkScan(serial);
         }
     }
 
@@ -961,4 +921,62 @@
         }
         // Only supported on AIDL.
     }
+
+    /**
+     * Enables or disables cellular identifier disclosure transparency.
+     *
+     * @param serial Serial number of request.
+     * @param enable Indicates whether to enable disclosure transparency or not.
+     */
+    public void setCellularIdentifierTransparencyEnabled(int serial, boolean enable)
+            throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.setCellularIdentifierTransparencyEnabled(serial, enable);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Checks whether cellular identifier transparency disclosure is enabled.
+     *
+     * @param serial Serial number of request.
+     */
+    public void isCellularIdentifierTransparencyEnabled(int serial) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.isCellularIdentifierTransparencyEnabled(serial);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Checks security algorithm update reports are enabled.
+     *
+     * @param serial Serial number of the request.
+     * @throws RemoteException
+     */
+    public void isSecurityAlgorithmsUpdatedEnabled(int serial) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.isSecurityAlgorithmsUpdatedEnabled(serial);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Enables or disables security algorithm update reports.
+     *
+     * @param serial Serial number of request.
+     * @param enable Indicates whether to enable or disable security algorithm update reports.
+     * @throws RemoteException
+     */
+    public void setSecurityAlgorithmsUpdatedEnabled(int serial,
+            boolean enable) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.setSecurityAlgorithmsUpdatedEnabled(serial, enable);
+        }
+        // Only supported on AIDL.
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index 0bc2958..ada45ec 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -43,11 +43,9 @@
 import android.telephony.BarringInfo;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellInfo;
-import android.telephony.LinkCapacityEstimate;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
 import android.telephony.NetworkScanRequest;
-import android.telephony.RadioAccessFamily;
 import android.telephony.RadioAccessSpecifier;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
@@ -103,7 +101,7 @@
      * @param cardStatus ICC card status as defined by CardStatus in types.hal
      */
     public void getIccCardStatusResponse(RadioResponseInfo responseInfo, CardStatus cardStatus) {
-        responseIccCardStatus(responseInfo, cardStatus);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -112,7 +110,7 @@
      */
     public void getIccCardStatusResponse_1_2(RadioResponseInfo responseInfo,
             android.hardware.radio.V1_2.CardStatus cardStatus) {
-        responseIccCardStatus_1_2(responseInfo, cardStatus);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -121,7 +119,7 @@
      */
     public void getIccCardStatusResponse_1_4(RadioResponseInfo responseInfo,
             android.hardware.radio.V1_4.CardStatus cardStatus) {
-        responseIccCardStatus_1_4(responseInfo, cardStatus);
+        responseIccCardStatus(responseInfo, cardStatus);
     }
 
     /**
@@ -208,7 +206,7 @@
      */
     public void getCurrentCallsResponse(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_0.Call> calls) {
-        responseCurrentCalls(responseInfo, calls);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -217,7 +215,7 @@
      */
     public void getCurrentCallsResponse_1_2(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_2.Call> calls) {
-        responseCurrentCalls_1_2(responseInfo, calls);
+        responseCurrentCalls(responseInfo, calls);
     }
 
     /**
@@ -301,27 +299,25 @@
 
     public void getSignalStrengthResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.V1_0.SignalStrength sigStrength) {
-        responseSignalStrength(responseInfo, sigStrength);
+        responseNotSupported(responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param signalStrength Current signal strength of camped/connected cells
      */
-    public void getSignalStrengthResponse_1_2(
-            RadioResponseInfo responseInfo,
+    public void getSignalStrengthResponse_1_2(RadioResponseInfo responseInfo,
             android.hardware.radio.V1_2.SignalStrength signalStrength) {
-        responseSignalStrength_1_2(responseInfo, signalStrength);
+        responseNotSupported(responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param signalStrength Current signal strength of camped/connected cells
      */
-    public void getSignalStrengthResponse_1_4(
-            RadioResponseInfo responseInfo,
+    public void getSignalStrengthResponse_1_4(RadioResponseInfo responseInfo,
             android.hardware.radio.V1_4.SignalStrength signalStrength) {
-        responseSignalStrength_1_4(responseInfo, signalStrength);
+        responseSignalStrength(responseInfo, signalStrength);
     }
 
     /**
@@ -341,14 +337,7 @@
      */
     public void getVoiceRegistrationStateResponse(RadioResponseInfo responseInfo,
             VoiceRegStateResult voiceRegResponse) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, voiceRegResponse);
-            }
-            mRil.processResponseDone(rr, responseInfo, voiceRegResponse);
-        }
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -422,14 +411,7 @@
      */
     public void getDataRegistrationStateResponse(RadioResponseInfo responseInfo,
             DataRegStateResult dataRegResponse) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, dataRegResponse);
-            }
-            mRil.processResponseDone(rr, responseInfo, dataRegResponse);
-        }
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -439,14 +421,7 @@
      */
     public void getDataRegistrationStateResponse_1_2(RadioResponseInfo responseInfo,
             android.hardware.radio.V1_2.DataRegStateResult dataRegResponse) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, dataRegResponse);
-            }
-            mRil.processResponseDone(rr, responseInfo, dataRegResponse);
-        }
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -584,7 +559,7 @@
      */
     public void setupDataCallResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.V1_0.SetupDataCallResult setupDataCallResult) {
-        responseSetupDataCall(responseInfo, setupDataCallResult);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -812,7 +787,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void startNetworkScanResponse(RadioResponseInfo responseInfo) {
-        responseScanStatus(responseInfo, null /*fallbackHalVersion*/);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -900,7 +875,7 @@
      */
     public void getDataCallListResponse(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_0.SetupDataCallResult> dataCallResultList) {
-        responseDataCallList(responseInfo, dataCallResultList);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -923,9 +898,6 @@
         responseDataCallList(responseInfo, dataCallResultList);
     }
 
-    public void sendOemRilRequestRawResponse(RadioResponseInfo responseInfo,
-            ArrayList<Byte> var2) {}
-
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
@@ -998,7 +970,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setPreferredNetworkTypeResponse(RadioResponseInfo responseInfo) {
-        responseVoid(responseInfo);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -1014,8 +986,7 @@
      * @param nwType RadioPreferredNetworkType defined in types.hal
      */
     public void getPreferredNetworkTypeResponse(RadioResponseInfo responseInfo, int nwType) {
-        mRil.mAllowedNetworkTypesBitmask = RadioAccessFamily.getRafFromNetworkType(nwType);
-        responseInts(responseInfo, RadioAccessFamily.getRafFromNetworkType(nwType));
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -1026,7 +997,6 @@
      */
     public void getPreferredNetworkTypeBitmapResponse(
             RadioResponseInfo responseInfo, int halRadioAccessFamilyBitmap) {
-
         int networkTypeBitmask = RILUtils.convertHalNetworkTypeBitMask(halRadioAccessFamilyBitmap);
         mRil.mAllowedNetworkTypesBitmask = networkTypeBitmask;
         responseInts(responseInfo, networkTypeBitmask);
@@ -1335,7 +1305,7 @@
 
     public void getCellInfoListResponse(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_0.CellInfo> cellInfo) {
-        responseCellInfoList(responseInfo, cellInfo);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -1344,7 +1314,7 @@
      */
     public void getCellInfoListResponse_1_2(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) {
-        responseCellInfoList(responseInfo, cellInfo);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -1453,8 +1423,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param result IccIoResult as defined in types.hal
      */
-    public void iccTransmitApduLogicalChannelResponse(
-            RadioResponseInfo responseInfo,
+    public void iccTransmitApduLogicalChannelResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.V1_0.IccIoResult result) {
         responseIccIo(responseInfo, result);
     }
@@ -1502,8 +1471,10 @@
         responseVoid(responseInfo);
     }
 
-    public void getHardwareConfigResponse(
-            RadioResponseInfo responseInfo,
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void getHardwareConfigResponse(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_0.HardwareConfig> config) {
         responseHardwareConfig(responseInfo, config);
     }
@@ -1577,7 +1548,7 @@
      * @param statusInfo LceStatusInfo indicating LCE status
      */
     public void startLceServiceResponse(RadioResponseInfo responseInfo, LceStatusInfo statusInfo) {
-        responseLceStatus(responseInfo, statusInfo);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -1585,11 +1556,11 @@
      * @param statusInfo LceStatusInfo indicating LCE status
      */
     public void stopLceServiceResponse(RadioResponseInfo responseInfo, LceStatusInfo statusInfo) {
-        responseLceStatus(responseInfo, statusInfo);
+        responseNotSupported(responseInfo);
     }
 
     public void pullLceDataResponse(RadioResponseInfo responseInfo, LceDataInfo lceInfo) {
-        responseLceData(responseInfo, lceInfo);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -1634,26 +1605,7 @@
      *        if Length of allowed carriers list is 0, numAllowed = 0.
      */
     public void setAllowedCarriersResponse(RadioResponseInfo responseInfo, int numAllowed) {
-        // The number of allowed carriers set correctly is not really useful. Even if one is
-        // missing, the operation has failed, as the list should be treated as a single
-        // configuration item. So, ignoring the value of numAllowed and considering only the
-        // value of the responseInfo.error.
-        int ret = TelephonyManager.SET_CARRIER_RESTRICTION_ERROR;
-        RILRequest rr = mRil.processResponse(responseInfo);
-        if (rr != null) {
-            mRil.riljLog("setAllowedCarriersResponse - error = " + responseInfo.error);
-
-            if (responseInfo.error == RadioError.NONE) {
-                ret = TelephonyManager.SET_CARRIER_RESTRICTION_SUCCESS;
-                sendMessageResponse(rr.mResult, ret);
-            } else if (responseInfo.error == RadioError.REQUEST_NOT_SUPPORTED) {
-                // Handle the case REQUEST_NOT_SUPPORTED as a valid response
-                responseInfo.error = RadioError.NONE;
-                ret = TelephonyManager.SET_CARRIER_RESTRICTION_NOT_SUPPORTED;
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -1681,13 +1633,7 @@
      */
     public void getAllowedCarriersResponse(RadioResponseInfo responseInfo, boolean allAllowed,
             CarrierRestrictions carriers) {
-        CarrierRestrictionsWithPriority carrierRestrictions = new CarrierRestrictionsWithPriority();
-        carrierRestrictions.allowedCarriers = carriers.allowedCarriers;
-        carrierRestrictions.excludedCarriers = carriers.excludedCarriers;
-        carrierRestrictions.allowedCarriersPrioritized = true;
-
-        responseCarrierRestrictions(responseInfo, allAllowed, carrierRestrictions,
-                SimLockMultiSimPolicy.NO_MULTISIM_POLICY);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -1696,11 +1642,36 @@
      * @param multiSimPolicy Policy for multi-sim devices.
      */
     public void getAllowedCarriersResponse_1_4(RadioResponseInfo responseInfo,
-            CarrierRestrictionsWithPriority carrierRestrictions,
-            int multiSimPolicy) {
-        // The API in IRadio 1.4 does not support the flag allAllowed, so setting it to false, so
-        // that values in carrierRestrictions are used.
-        responseCarrierRestrictions(responseInfo, false, carrierRestrictions, multiSimPolicy);
+            CarrierRestrictionsWithPriority carrierRestrictions, int multiSimPolicy) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr == null) {
+            return;
+        }
+
+        int policy = CarrierRestrictionRules.MULTISIM_POLICY_NONE;
+        if (multiSimPolicy == SimLockMultiSimPolicy.ONE_VALID_SIM_MUST_BE_PRESENT) {
+            policy = CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT;
+        }
+
+        int carrierRestrictionDefault =
+                CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
+        if (!carrierRestrictions.allowedCarriersPrioritized) {
+            carrierRestrictionDefault = CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED;
+        }
+
+        CarrierRestrictionRules ret = CarrierRestrictionRules.newBuilder()
+                .setAllowedCarriers(RILUtils.convertHalCarrierList(
+                        carrierRestrictions.allowedCarriers))
+                .setExcludedCarriers(RILUtils.convertHalCarrierList(
+                        carrierRestrictions.excludedCarriers))
+                .setDefaultCarrierRestriction(carrierRestrictionDefault)
+                .setMultiSimPolicy(policy)
+                .build();
+
+        if (responseInfo.error == RadioError.NONE) {
+            sendMessageResponse(rr.mResult, ret);
+        }
+        mRil.processResponseDone(rr, responseInfo, ret);
     }
 
     /**
@@ -1735,7 +1706,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setSimCardPowerResponse(RadioResponseInfo responseInfo) {
-        responseVoid(responseInfo);
+        responseNotSupported(responseInfo);
     }
 
     /**
@@ -1874,39 +1845,7 @@
         }
     }
 
-    private void responseIccCardStatus(RadioResponseInfo responseInfo, CardStatus cardStatus) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            IccCardStatus iccCardStatus = RILUtils.convertHalCardStatus(cardStatus);
-            mRil.riljLog("responseIccCardStatus: from HIDL: " + iccCardStatus);
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, iccCardStatus);
-            }
-            mRil.processResponseDone(rr, responseInfo, iccCardStatus);
-        }
-    }
-
-    private void responseIccCardStatus_1_2(RadioResponseInfo responseInfo,
-            android.hardware.radio.V1_2.CardStatus cardStatus) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            IccCardStatus iccCardStatus = RILUtils.convertHalCardStatus(cardStatus.base);
-            IccSlotPortMapping slotPortMapping = new IccSlotPortMapping();
-            slotPortMapping.mPhysicalSlotIndex = cardStatus.physicalSlotId;
-            iccCardStatus.mSlotPortMapping = slotPortMapping;
-            iccCardStatus.atr = cardStatus.atr;
-            iccCardStatus.iccid = cardStatus.iccid;
-            mRil.riljLog("responseIccCardStatus: from HIDL: " + iccCardStatus);
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, iccCardStatus);
-            }
-            mRil.processResponseDone(rr, responseInfo, iccCardStatus);
-        }
-    }
-
-    private void responseIccCardStatus_1_4(RadioResponseInfo responseInfo,
+    private void responseIccCardStatus(RadioResponseInfo responseInfo,
             android.hardware.radio.V1_4.CardStatus cardStatus) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
@@ -2035,52 +1974,12 @@
     }
 
     private void responseCurrentCalls(RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_0.Call> calls) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            int num = calls.size();
-            ArrayList<DriverCall> dcCalls = new ArrayList<DriverCall>(num);
-            DriverCall dc;
-
-            for (int i = 0; i < num; i++) {
-                dc = RILUtils.convertToDriverCall(calls.get(i));
-
-                dcCalls.add(dc);
-
-                if (dc.isVoicePrivacy) {
-                    mRil.mVoicePrivacyOnRegistrants.notifyRegistrants();
-                    mRil.riljLog("InCall VoicePrivacy is enabled");
-                } else {
-                    mRil.mVoicePrivacyOffRegistrants.notifyRegistrants();
-                    mRil.riljLog("InCall VoicePrivacy is disabled");
-                }
-            }
-
-            Collections.sort(dcCalls);
-
-            if ((num == 0) && mRil.mTestingEmergencyCall.getAndSet(false)) {
-                if (mRil.mEmergencyCallbackModeRegistrant != null) {
-                    mRil.riljLog("responseCurrentCalls: call ended, testing emergency call,"
-                            + " notify ECM Registrants");
-                    mRil.mEmergencyCallbackModeRegistrant.notifyRegistrant();
-                }
-            }
-
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, dcCalls);
-            }
-            mRil.processResponseDone(rr, responseInfo, dcCalls);
-        }
-    }
-
-    private void responseCurrentCalls_1_2(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_2.Call> calls) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
             int num = calls.size();
-            ArrayList<DriverCall> dcCalls = new ArrayList<DriverCall>(num);
+            ArrayList<DriverCall> dcCalls = new ArrayList<>(num);
             DriverCall dc;
 
             for (int i = 0; i < num; i++) {
@@ -2155,6 +2054,15 @@
         }
     }
 
+    private void responseNotSupported(RadioResponseInfo responseInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            mRil.riljLog(RILUtils.requestToString(rr.mRequest) + "not supported on IRadio < 1.4");
+            responseInfo.error = RadioError.REQUEST_NOT_SUPPORTED;
+            mRil.processResponseDone(rr, responseInfo, null);
+        }
+    }
+
     private void responseVoid(RadioResponseInfo responseInfo) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
@@ -2301,34 +2209,6 @@
 
     private void responseSignalStrength(
             RadioResponseInfo responseInfo,
-            android.hardware.radio.V1_0.SignalStrength signalStrength) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            SignalStrength ret = RILUtils.convertHalSignalStrength(signalStrength);
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    private void responseSignalStrength_1_2(
-            RadioResponseInfo responseInfo,
-            android.hardware.radio.V1_2.SignalStrength signalStrength) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            SignalStrength ret = RILUtils.convertHalSignalStrength(signalStrength);
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    private void responseSignalStrength_1_4(
-            RadioResponseInfo responseInfo,
             android.hardware.radio.V1_4.SignalStrength signalStrength) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
@@ -2696,69 +2576,6 @@
         }
     }
 
-    private void responseLceStatus(RadioResponseInfo responseInfo, LceStatusInfo statusInfo) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            ArrayList<Integer> ret = new ArrayList<>();
-            ret.add(statusInfo.lceStatus);
-            ret.add(Byte.toUnsignedInt(statusInfo.actualIntervalMs));
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    private void responseLceData(RadioResponseInfo responseInfo, LceDataInfo lceInfo) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            List<LinkCapacityEstimate> ret = RILUtils.convertHalLceData(lceInfo);
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    private void responseCarrierRestrictions(RadioResponseInfo responseInfo, boolean allAllowed,
-            CarrierRestrictionsWithPriority carriers, int multiSimPolicy) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-        if (rr == null) {
-            return;
-        }
-        CarrierRestrictionRules ret;
-
-        if (allAllowed) {
-            ret = CarrierRestrictionRules.newBuilder().setAllCarriersAllowed().build();
-        } else {
-            int policy = CarrierRestrictionRules.MULTISIM_POLICY_NONE;
-            if (multiSimPolicy == SimLockMultiSimPolicy.ONE_VALID_SIM_MUST_BE_PRESENT) {
-                policy = CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT;
-            }
-
-            int carrierRestrictionDefault =
-                    CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
-            if (!carriers.allowedCarriersPrioritized) {
-                carrierRestrictionDefault =
-                        CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED;
-            }
-
-            ret = CarrierRestrictionRules.newBuilder()
-                    .setAllowedCarriers(RILUtils.convertHalCarrierList(carriers.allowedCarriers))
-                    .setExcludedCarriers(RILUtils.convertHalCarrierList(carriers.excludedCarriers))
-                    .setDefaultCarrierRestriction(carrierRestrictionDefault)
-                    .setMultiSimPolicy(policy)
-                    .build();
-        }
-
-        if (responseInfo.error == RadioError.NONE) {
-            sendMessageResponse(rr.mResult, ret);
-        }
-        mRil.processResponseDone(rr, responseInfo, ret);
-    }
-
     /**
      * @param responseInfo Response info struct containing response type, serial number and error.
      */
diff --git a/src/java/com/android/internal/telephony/RadioServiceProxy.java b/src/java/com/android/internal/telephony/RadioServiceProxy.java
index 4257327..02fc751 100644
--- a/src/java/com/android/internal/telephony/RadioServiceProxy.java
+++ b/src/java/com/android/internal/telephony/RadioServiceProxy.java
@@ -19,13 +19,13 @@
 import android.os.RemoteException;
 
 /**
- * A holder for IRadio services. Use getHidl to get IRadio 1.0 and call the HIDL implementations or
- * getAidl to get the AIDL service and call the AIDL implementations of the HAL APIs.
+ * A holder for IRadio services.
+ * Use getHidl to get the HIDL IRadio service or getAidl to get the corresponding AIDL service.
  */
 public abstract class RadioServiceProxy {
     boolean mIsAidl;
     HalVersion mHalVersion = RIL.RADIO_HAL_VERSION_UNKNOWN;
-    volatile android.hardware.radio.V1_0.IRadio mRadioProxy = null;
+    volatile android.hardware.radio.V1_4.IRadio mRadioProxy = null;
 
     /**
      * Whether RadioServiceProxy is an AIDL or HIDL implementation
@@ -40,7 +40,7 @@
      * @param halVersion Radio HAL version
      * @param radio      IRadio implementation
      */
-    public void setHidl(HalVersion halVersion, android.hardware.radio.V1_0.IRadio radio) {
+    public void setHidl(HalVersion halVersion, android.hardware.radio.V1_4.IRadio radio) {
         mHalVersion = halVersion;
         mRadioProxy = radio;
         mIsAidl = false;
@@ -50,7 +50,7 @@
      * Get the HIDL implementation of RadioServiceProxy
      * @return IRadio implementation
      */
-    public android.hardware.radio.V1_0.IRadio getHidl() {
+    public android.hardware.radio.V1_4.IRadio getHidl() {
         return mRadioProxy;
     }
 
diff --git a/src/java/com/android/internal/telephony/RadioSimProxy.java b/src/java/com/android/internal/telephony/RadioSimProxy.java
index 7c8ee7b..5265692 100644
--- a/src/java/com/android/internal/telephony/RadioSimProxy.java
+++ b/src/java/com/android/internal/telephony/RadioSimProxy.java
@@ -16,22 +16,17 @@
 
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.RILConstants.REQUEST_NOT_SUPPORTED;
-
-import android.os.AsyncResult;
-import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.Rlog;
-import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
 
 /**
- * A holder for IRadioSim. Use getHidl to get IRadio 1.0 and call the HIDL implementations or
- * getAidl to get IRadioSim and call the AIDL implementations of the HAL APIs.
+ * A holder for IRadioSim.
+ * Use getAidl to get IRadioSim and call the AIDL implementations of the HAL APIs.
  */
 public class RadioSimProxy extends RadioServiceProxy {
     private static final String TAG = "RadioSimProxy";
@@ -160,10 +155,8 @@
         if (isEmpty()) return;
         if (isAidl()) {
             mSimProxy.getAllowedCarriers(serial);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
-            ((android.hardware.radio.V1_4.IRadio) mRadioProxy).getAllowedCarriers_1_4(serial);
         } else {
-            mRadioProxy.getAllowedCarriers(serial);
+            mRadioProxy.getAllowedCarriers_1_4(serial);
         }
     }
 
@@ -521,11 +514,10 @@
      * Call IRadioSim#setAllowedCarriers
      * @param serial Serial number of request
      * @param carrierRestrictionRules Allowed carriers
-     * @param result Result to return in case of error
      * @throws RemoteException
      */
-    public void setAllowedCarriers(int serial, CarrierRestrictionRules carrierRestrictionRules,
-            Message result) throws RemoteException {
+    public void setAllowedCarriers(int serial, CarrierRestrictionRules carrierRestrictionRules)
+            throws RemoteException {
         if (isEmpty()) return;
         if (isAidl()) {
             // Prepare structure with allowed list, excluded list and priority
@@ -541,7 +533,7 @@
             mSimProxy.setAllowedCarriers(serial, carrierRestrictions,
                     RILUtils.convertToHalSimLockMultiSimPolicyAidl(
                             carrierRestrictionRules.getMultiSimPolicy()));
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)) {
+        } else {
             // Prepare structure with allowed list, excluded list and priority
             android.hardware.radio.V1_4.CarrierRestrictionsWithPriority carrierRestrictions =
                     new android.hardware.radio.V1_4.CarrierRestrictionsWithPriority();
@@ -552,35 +544,9 @@
             carrierRestrictions.allowedCarriersPrioritized =
                     (carrierRestrictionRules.getDefaultCarrierRestriction()
                             == CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED);
-            ((android.hardware.radio.V1_4.IRadio) mRadioProxy).setAllowedCarriers_1_4(
-                    serial, carrierRestrictions, RILUtils.convertToHalSimLockMultiSimPolicy(
+            mRadioProxy.setAllowedCarriers_1_4(serial, carrierRestrictions,
+                    RILUtils.convertToHalSimLockMultiSimPolicy(
                             carrierRestrictionRules.getMultiSimPolicy()));
-        } else {
-            boolean isAllCarriersAllowed = carrierRestrictionRules.isAllCarriersAllowed();
-            boolean supported = (isAllCarriersAllowed
-                    || (carrierRestrictionRules.getExcludedCarriers().isEmpty()
-                    && (carrierRestrictionRules.getDefaultCarrierRestriction()
-                    == CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED)))
-                    && (RILUtils.convertToHalSimLockMultiSimPolicy(
-                    carrierRestrictionRules.getMultiSimPolicy())
-                    == android.hardware.radio.V1_4.SimLockMultiSimPolicy.NO_MULTISIM_POLICY);
-
-            if (!supported) {
-                // Feature is not supported by IRadio interface
-                if (result != null) {
-                    AsyncResult.forMessage(result, null,
-                            CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                    result.sendToTarget();
-                }
-                return;
-            }
-
-            // Prepare structure with allowed list
-            android.hardware.radio.V1_0.CarrierRestrictions carrierRestrictions =
-                    new android.hardware.radio.V1_0.CarrierRestrictions();
-            carrierRestrictions.allowedCarriers = RILUtils.convertToHalCarrierRestrictionList(
-                    carrierRestrictionRules.getAllowedCarriers());
-            mRadioProxy.setAllowedCarriers(serial, isAllCarriersAllowed, carrierRestrictions);
         }
     }
 
@@ -592,7 +558,7 @@
      */
     public void setCarrierInfoForImsiEncryption(int serial, ImsiEncryptionInfo imsiEncryptionInfo)
             throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_1)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             android.hardware.radio.sim.ImsiEncryptionInfo halImsiInfo =
                     new android.hardware.radio.sim.ImsiEncryptionInfo();
@@ -635,8 +601,7 @@
                 halImsiInfo.carrierKey.add(Byte.valueOf(b));
             }
 
-            ((android.hardware.radio.V1_1.IRadio) mRadioProxy).setCarrierInfoForImsiEncryption(
-                    serial, halImsiInfo);
+            mRadioProxy.setCarrierInfoForImsiEncryption(serial, halImsiInfo);
         }
     }
 
@@ -681,35 +646,16 @@
      * Call IRadioSim#setSimCardPower
      * @param serial Serial number of request
      * @param state SIM state (power down, power up, pass through)
-     * @param result Result to return in case of error
      * @throws RemoteException
      */
-    public void setSimCardPower(int serial, int state, Message result) throws RemoteException {
+    public void setSimCardPower(int serial, int state) throws RemoteException {
         if (isEmpty()) return;
         if (isAidl()) {
             mSimProxy.setSimCardPower(serial, state);
         } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)) {
             ((android.hardware.radio.V1_6.IRadio) mRadioProxy).setSimCardPower_1_6(serial, state);
-        } else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_1)) {
-            ((android.hardware.radio.V1_1.IRadio) mRadioProxy).setSimCardPower_1_1(serial, state);
         } else {
-            switch (state) {
-                case TelephonyManager.CARD_POWER_DOWN: {
-                    mRadioProxy.setSimCardPower(serial, false);
-                    break;
-                }
-                case TelephonyManager.CARD_POWER_UP: {
-                    mRadioProxy.setSimCardPower(serial, true);
-                    break;
-                }
-                default: {
-                    if (result != null) {
-                        AsyncResult.forMessage(result, null,
-                                CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                        result.sendToTarget();
-                    }
-                }
-            }
+            mRadioProxy.setSimCardPower_1_1(serial, state);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/RadioVoiceProxy.java b/src/java/com/android/internal/telephony/RadioVoiceProxy.java
index 7f46424..e57a61d 100644
--- a/src/java/com/android/internal/telephony/RadioVoiceProxy.java
+++ b/src/java/com/android/internal/telephony/RadioVoiceProxy.java
@@ -24,8 +24,8 @@
 import java.util.ArrayList;
 
 /**
- * A holder for IRadioVoice. Use getHidl to get IRadio 1.0 and call the HIDL implementations or
- * getAidl to get IRadioVoice and call the AIDL implementations of the HAL APIs.
+ * A holder for IRadioVoice.
+ * Use getAidl to get IRadioVoice and call the AIDL implementations of the HAL APIs.
  */
 public class RadioVoiceProxy extends RadioServiceProxy {
     private static final String TAG = "RadioVoiceProxy";
@@ -153,7 +153,7 @@
     public void emergencyDial(int serial, String address, EmergencyNumber emergencyNumberInfo,
             boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo)
             throws RemoteException {
-        if (isEmpty() || mHalVersion.less(RIL.RADIO_HAL_VERSION_1_4)) return;
+        if (isEmpty()) return;
         if (isAidl()) {
             mVoiceProxy.emergencyDial(serial,
                     RILUtils.convertToHalDialAidl(address, clirMode, uusInfo),
@@ -177,7 +177,7 @@
                     emergencyNumberInfo.getEmergencyNumberSourceBitmask()
                             == EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST);
         } else {
-            ((android.hardware.radio.V1_4.IRadio) mRadioProxy).emergencyDial(serial,
+            mRadioProxy.emergencyDial(serial,
                     RILUtils.convertToHalDial(address, clirMode, uusInfo),
                     emergencyNumberInfo.getEmergencyServiceCategoryBitmaskInternalDial(),
                     emergencyNumberInfo.getEmergencyUrns() != null
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index a78242a..04f5c08 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -86,6 +86,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.analytics.TelephonyAnalytics;
+import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
 import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
@@ -1008,6 +1010,16 @@
     protected abstract boolean shouldBlockSmsForEcbm();
 
     /**
+     * Notifies the {@link SmsDispatchersController} that sending MO SMS is failed.
+     *
+     * @param tracker holds the SMS message to be sent
+     */
+    protected void notifySmsSentFailedToEmergencyStateTracker(SmsTracker tracker) {
+        mSmsDispatchersController.notifySmsSentFailedToEmergencyStateTracker(
+                tracker.mDestAddress, tracker.mMessageId);
+    }
+
+    /**
      * Called when SMS send completes. Broadcasts a sentIntent on success.
      * On failure, either sets up retries or broadcasts a sentIntent with
      * the failure in the result code.
@@ -1039,6 +1051,8 @@
             }
             tracker.onSent(mContext);
             mPhone.notifySmsSent(tracker.mDestAddress);
+            mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
+                    tracker.mDestAddress, tracker.mMessageId);
 
             mPhone.getSmsStats().onOutgoingSms(
                     tracker.mImsRetry > 0 /* isOverIms */,
@@ -1048,6 +1062,19 @@
                     tracker.mMessageId,
                     tracker.isFromDefaultSmsApplication(mContext),
                     tracker.getInterval());
+            if (mPhone != null) {
+                TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                if (telephonyAnalytics != null) {
+                    SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                    if (smsMmsAnalytics != null) {
+                        smsMmsAnalytics.onOutgoingSms(
+                                tracker.mImsRetry > 0 /* isOverIms */,
+                                SmsManager.RESULT_ERROR_NONE
+                        );
+                    }
+                }
+            }
+
         } else {
             if (DBG) {
                 Rlog.d(TAG, "SMS send failed "
@@ -1076,6 +1103,7 @@
             // if sms over IMS is not supported on data and voice is not available...
             if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
                 tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
+                notifySmsSentFailedToEmergencyStateTracker(tracker);
                 mPhone.getSmsStats().onOutgoingSms(
                         tracker.mImsRetry > 0 /* isOverIms */,
                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
@@ -1084,6 +1112,19 @@
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
                         tracker.getInterval());
+                if (mPhone != null) {
+                    TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                    if (telephonyAnalytics != null) {
+                        SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                        if (smsMmsAnalytics != null) {
+                            smsMmsAnalytics.onOutgoingSms(
+                                    tracker.mImsRetry > 0 /* isOverIms */,
+                                    getNotInServiceError(ss)
+                            );
+                        }
+                    }
+                }
+
             } else if (error == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY
                     && tracker.mRetryCount < getMaxSmsRetryCount()) {
                 // Retry after a delay if needed.
@@ -1107,9 +1148,23 @@
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
                         tracker.getInterval());
+                if (mPhone != null) {
+                    TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                    if (telephonyAnalytics != null) {
+                        SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                        if (smsMmsAnalytics != null) {
+                            smsMmsAnalytics.onOutgoingSms(
+                                    tracker.mImsRetry > 0 /* isOverIms */,
+                                    SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY
+                            );
+                        }
+                    }
+                }
+
             } else {
                 int errorCode = (smsResponse != null) ? smsResponse.mErrorCode : NO_ERROR_CODE;
                 tracker.onFailed(mContext, error, errorCode);
+                notifySmsSentFailedToEmergencyStateTracker(tracker);
                 mPhone.getSmsStats().onOutgoingSms(
                         tracker.mImsRetry > 0 /* isOverIms */,
                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
@@ -1119,6 +1174,17 @@
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
                         tracker.getInterval());
+                if (mPhone != null) {
+                    TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                    if (telephonyAnalytics != null) {
+                        SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                        if (smsMmsAnalytics != null) {
+                            smsMmsAnalytics.onOutgoingSms(
+                                    tracker.mImsRetry > 0 /* isOverIms */,
+                                    error);
+                        }
+                    }
+                }
             }
         }
     }
@@ -2323,6 +2389,7 @@
             int errorCode) {
         for (SmsTracker tracker : trackers) {
             tracker.onFailed(mContext, error, errorCode);
+            notifySmsSentFailedToEmergencyStateTracker(tracker);
         }
         if (trackers.length > 0) {
             // This error occurs before the SMS is sent. Make an assumption if it would have
@@ -2335,6 +2402,17 @@
                     trackers[0].mMessageId,
                     trackers[0].isFromDefaultSmsApplication(mContext),
                     trackers[0].getInterval());
+            if (mPhone != null) {
+                TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                if (telephonyAnalytics != null) {
+                    SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
+                    if (smsMmsAnalytics != null) {
+                        smsMmsAnalytics.onOutgoingSms(
+                                isIms(),
+                                error);
+                    }
+                }
+            }
         }
     }
 
@@ -2466,7 +2544,13 @@
         /** Return if the SMS was originated from the default SMS application. */
         public boolean isFromDefaultSmsApplication(Context context) {
             if (mIsFromDefaultSmsApplication == null) {
-                UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(context, mSubId);
+                UserHandle userHandle;
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    userHandle = TelephonyUtils.getSubscriptionUserHandle(context, mSubId);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
                 // Perform a lazy initialization, due to the cost of the operation.
                 mIsFromDefaultSmsApplication = SmsApplication.isDefaultSmsApplicationAsUser(context,
                                     getAppPackageName(), userHandle);
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 8a49670..88d541c 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -94,10 +94,15 @@
 import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
 import com.android.internal.telephony.data.DataNetwork;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.metrics.RadioPowerStateStats;
 import com.android.internal.telephony.metrics.ServiceStateStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.satellite.NtnCapabilityResolver;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
@@ -623,7 +628,8 @@
      */
     private AccessNetworksManagerCallback mAccessNetworksManagerCallback = null;
 
-    public ServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci) {
+    public ServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci,
+            FeatureFlags featureFlags) {
         mNitzState = TelephonyComponentFactory.getInstance()
                 .inject(NitzStateMachine.class.getName())
                 .makeNitzStateMachine(phone);
@@ -675,7 +681,7 @@
         }
         mLocaleTracker = TelephonyComponentFactory.getInstance()
                 .inject(LocaleTracker.class.getName())
-                .makeLocaleTracker(mPhone, mNitzState, getLooper());
+                .makeLocaleTracker(mPhone, mNitzState, getLooper(), featureFlags);
 
         mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
         mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
@@ -685,7 +691,7 @@
         // system setting property AIRPLANE_MODE_ON is set in Settings.
         int airplaneMode = Settings.Global.getInt(mCr, Settings.Global.AIRPLANE_MODE_ON, 0);
         int enableCellularOnBoot = Settings.Global.getInt(mCr,
-                Settings.Global.ENABLE_CELLULAR_ON_BOOT, 1);
+                Settings.Global.ENABLE_CELLULAR_ON_BOOT, getDefaultEnableCellularOnBoot());
         mDesiredPowerState = (enableCellularOnBoot > 0) && ! (airplaneMode > 0);
         if (!mDesiredPowerState) {
             mRadioPowerOffReasons.add(TelephonyManager.RADIO_POWER_REASON_USER);
@@ -708,7 +714,7 @@
         mCi.setOnRestrictedStateChanged(this, EVENT_RESTRICTED_STATE_CHANGED, null);
         updatePhoneType();
 
-        mCSST = new CarrierServiceStateTracker(phone, this);
+        mCSST = new CarrierServiceStateTracker(phone, this, featureFlags);
 
         registerForNetworkAttached(mCSST,
                 CarrierServiceStateTracker.CARRIER_EVENT_VOICE_REGISTRATION, null);
@@ -749,6 +755,11 @@
         }
     }
 
+    private int getDefaultEnableCellularOnBoot() {
+        return mPhone.getContext().getResources().getBoolean(
+            R.bool.config_enable_cellular_on_boot_default) ? 1 : 0;
+    }
+
     @VisibleForTesting
     public void updatePhoneType() {
 
@@ -1325,6 +1336,8 @@
                 break;
 
             case EVENT_RADIO_STATE_CHANGED:
+                RadioPowerStateStats.onRadioStateChanged(mCi.getRadioState());
+                // fall through, the code above only logs metrics when radio state changes
             case EVENT_PHONE_TYPE_SWITCHED:
                 if(!mPhone.isPhoneTypeGsm() &&
                         mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
@@ -1937,16 +1950,15 @@
                 err = ((CommandException)(ar.exception)).getCommandError();
             }
 
-            if (mCi.getRadioState() != TelephonyManager.RADIO_POWER_ON) {
-                log("handlePollStateResult: Invalid response due to radio off or unavailable. "
-                        + "Set ServiceState to out of service.");
-                pollStateInternal(false);
-                return;
-            }
-
             if (err == CommandException.Error.RADIO_NOT_AVAILABLE) {
-                loge("handlePollStateResult: RIL returned RADIO_NOT_AVAILABLE when radio is on.");
-                cancelPollState();
+                loge("handlePollStateResult: RIL returned RADIO_NOT_AVAILABLE.");
+                if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
+                    cancelPollState();
+                } else {
+                    handlePollStateInternalForRadioOffOrUnavailable(
+                            mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
+                    pollStateDone();
+                }
                 return;
             }
 
@@ -3303,42 +3315,17 @@
 
     private void pollStateInternal(boolean modemTriggered) {
         mPollingContext = new int[1];
-        NetworkRegistrationInfo nri;
 
         log("pollState: modemTriggered=" + modemTriggered + ", radioState=" + mCi.getRadioState());
 
         switch (mCi.getRadioState()) {
             case TelephonyManager.RADIO_POWER_UNAVAILABLE:
-                // Preserve the IWLAN registration state, because that should not be affected by
-                // radio availability.
-                nri = mNewSS.getNetworkRegistrationInfo(
-                        NetworkRegistrationInfo.DOMAIN_PS,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-                mNewSS.setOutOfService(false);
-                // Add the IWLAN registration info back to service state.
-                if (nri != null) {
-                    mNewSS.addNetworkRegistrationInfo(nri);
-                }
-                mPhone.getSignalStrengthController().setSignalStrengthDefaultValues();
-                mLastNitzData = null;
-                mNitzState.handleNetworkUnavailable();
+                handlePollStateInternalForRadioOffOrUnavailable(false);
                 pollStateDone();
                 break;
 
             case TelephonyManager.RADIO_POWER_OFF:
-                // Preserve the IWLAN registration state, because that should not be affected by
-                // radio availability.
-                nri = mNewSS.getNetworkRegistrationInfo(
-                        NetworkRegistrationInfo.DOMAIN_PS,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-                mNewSS.setOutOfService(true);
-                // Add the IWLAN registration info back to service state.
-                if (nri != null) {
-                    mNewSS.addNetworkRegistrationInfo(nri);
-                }
-                mPhone.getSignalStrengthController().setSignalStrengthDefaultValues();
-                mLastNitzData = null;
-                mNitzState.handleNetworkUnavailable();
+                handlePollStateInternalForRadioOffOrUnavailable(true);
                 // Don't poll when device is shutting down or the poll was not modemTriggered
                 // (they sent us new radio data) and the current network is not IWLAN
                 if (mDeviceShuttingDown ||
@@ -3382,6 +3369,21 @@
         }
     }
 
+    private void handlePollStateInternalForRadioOffOrUnavailable(boolean radioOff) {
+        // Preserve the IWLAN registration state, which should not be affected by radio availability
+        NetworkRegistrationInfo nri = mNewSS.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        mNewSS.setOutOfService(radioOff);
+        // Add the IWLAN registration info back to service state.
+        if (nri != null) {
+            mNewSS.addNetworkRegistrationInfo(nri);
+        }
+        mPhone.getSignalStrengthController().setSignalStrengthDefaultValues();
+        mLastNitzData = null;
+        mNitzState.handleNetworkUnavailable();
+    }
+
     /**
      * Get the highest-priority CellIdentity for a provided ServiceState.
      *
@@ -4968,6 +4970,10 @@
      */
     public void powerOffRadioSafely() {
         synchronized (this) {
+            SatelliteController.getInstance().onCellularRadioPowerOffRequested();
+            if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+                EmergencyStateTracker.getInstance().onCellularRadioPowerOffRequested();
+            }
             if (!mPendingRadioPowerOffAfterDataOff) {
                 // hang up all active voice calls first
                 if (mPhone.isPhoneTypeGsm() && mPhone.isInCall()) {
@@ -5053,7 +5059,6 @@
         }
 
         mCi.setRadioPower(false, obtainMessage(EVENT_RADIO_POWER_OFF_DONE));
-
     }
 
     /** Cancel a pending (if any) pollState() operation */
diff --git a/src/java/com/android/internal/telephony/SignalStrengthController.java b/src/java/com/android/internal/telephony/SignalStrengthController.java
index 705dab4..b11d7e5 100644
--- a/src/java/com/android/internal/telephony/SignalStrengthController.java
+++ b/src/java/com/android/internal/telephony/SignalStrengthController.java
@@ -30,6 +30,7 @@
 import android.os.RegistrantList;
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityLte;
@@ -61,7 +62,9 @@
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.PatternSyntaxException;
 
 /**
@@ -99,6 +102,7 @@
     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;
+    private static final int EVENT_SERVICE_STATE_CHANGED                    = 10;
 
     @NonNull
     private final Phone mPhone;
@@ -144,6 +148,8 @@
     @NonNull
     private final LocalLog mLocalLog = new LocalLog(64);
 
+    private final AtomicBoolean mNTNConnected = new AtomicBoolean(false);
+
     public SignalStrengthController(@NonNull Phone phone) {
         mPhone = phone;
         mCi = mPhone.mCi;
@@ -159,6 +165,8 @@
         ccm.registerCarrierConfigChangeListener(this::post,
                 (slotIndex, subId, carrierId, specificCarrierId) ->
                         onCarrierConfigurationChanged(slotIndex));
+
+        mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
     }
 
     @Override
@@ -271,6 +279,11 @@
                 break;
             }
 
+            case EVENT_SERVICE_STATE_CHANGED: {
+                onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result);
+                break;
+            }
+
             default:
                 log("Unhandled message with number: " + msg.what);
                 break;
@@ -294,31 +307,38 @@
     }
 
     /**
-     * send signal-strength-changed notification if changed Called both for
-     * solicited and unsolicited signal strength updates
-     *
-     * @return true if the signal strength changed and a notification was sent.
+     * Send signal-strength-changed notification if changed. Called for both solicited and
+     * unsolicited signal strength updates.
      */
-    private boolean onSignalStrengthResult(@NonNull AsyncResult ar) {
+    private void onSignalStrengthResult(@NonNull AsyncResult ar) {
+        // This signal is used for both voice and data radio signal so parse all fields.
 
-        // This signal is used for both voice and data radio signal so parse
-        // all fields
-
+        SignalStrength signalStrength;
         if ((ar.exception == null) && (ar.result != null)) {
-            mSignalStrength = (SignalStrength) ar.result;
-
-            if (mPhone.getServiceStateTracker() != null) {
-                mSignalStrength.updateLevel(mCarrierConfig, mPhone.getServiceStateTracker().mSS);
-            }
+            signalStrength = (SignalStrength) ar.result;
         } else {
-            log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
-            mSignalStrength = new SignalStrength();
+            loge("onSignalStrengthResult() Exception from RIL : " + ar.exception);
+            signalStrength = new SignalStrength();
+        }
+        updateSignalStrength(signalStrength);
+    }
+
+    /**
+     * Set {@code mSignalStrength} to the input argument {@code signalStrength}, update its level,
+     * and send signal-strength-changed notification if changed.
+     *
+     * @param signalStrength The new SignalStrength used for updating {@code mSignalStrength}.
+     */
+    private void updateSignalStrength(@NonNull SignalStrength signalStrength) {
+        mSignalStrength = signalStrength;
+        ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
+        if (serviceStateTracker != null) {
+            mSignalStrength.updateLevel(mCarrierConfig, serviceStateTracker.mSS);
+        } else {
+            loge("updateSignalStrength: serviceStateTracker is null");
         }
         mSignalStrengthUpdatedTime = System.currentTimeMillis();
-
-        boolean ssChanged = notifySignalStrength();
-
-        return ssChanged;
+        notifySignalStrength();
     }
 
     /**
@@ -344,7 +364,7 @@
 
         List<SubscriptionInfo> subInfoList = SubscriptionManagerService.getInstance()
                 .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
-                        mPhone.getContext().getAttributionTag());
+                        mPhone.getContext().getAttributionTag(), true/*isForAllProfile*/);
 
         if (!ArrayUtils.isEmpty(subInfoList)) {
             for (SubscriptionInfo info : subInfoList) {
@@ -377,9 +397,10 @@
                 CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY);
         if (gsmRssiThresholds != null) {
             signalThresholdInfos.add(
-                    createSignalThresholdsInfo(
+                    validateAndCreateSignalThresholdInfo(
                             SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
                             gsmRssiThresholds,
+                            AccessNetworkThresholds.GERAN,
                             AccessNetworkConstants.AccessNetworkType.GERAN,
                             true));
         }
@@ -388,45 +409,54 @@
                 CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY);
         if (wcdmaRscpThresholds != null) {
             signalThresholdInfos.add(
-                    createSignalThresholdsInfo(
+                    validateAndCreateSignalThresholdInfo(
                             SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
                             wcdmaRscpThresholds,
+                            AccessNetworkThresholds.UTRAN,
                             AccessNetworkConstants.AccessNetworkType.UTRAN,
                             true));
         }
 
-        int lteMeasurementEnabled = mCarrierConfig.getInt(CarrierConfigManager
-                .KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP);
-        int[] lteRsrpThresholds = mCarrierConfig.getIntArray(
-                CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
+        int lteMeasurementEnabled = mCarrierConfig.getInt(isUsingNonTerrestrialNetwork()
+                        ? CarrierConfigManager.KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT
+                        : CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
+                CellSignalStrengthLte.USE_RSRP);
+        int[] lteRsrpThresholds = mCarrierConfig.getIntArray(isUsingNonTerrestrialNetwork()
+                ? CarrierConfigManager.KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY
+                : CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
         if (lteRsrpThresholds != null) {
             signalThresholdInfos.add(
-                    createSignalThresholdsInfo(
+                    validateAndCreateSignalThresholdInfo(
                             SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
                             lteRsrpThresholds,
+                            AccessNetworkThresholds.EUTRAN_RSRP,
                             AccessNetworkConstants.AccessNetworkType.EUTRAN,
                             (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRP) != 0));
         }
 
         if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
-            int[] lteRsrqThresholds = mCarrierConfig.getIntArray(
+            int[] lteRsrqThresholds = mCarrierConfig.getIntArray(isUsingNonTerrestrialNetwork()
+                    ? CarrierConfigManager.KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY :
                     CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY);
             if (lteRsrqThresholds != null) {
                 signalThresholdInfos.add(
-                        createSignalThresholdsInfo(
+                        validateAndCreateSignalThresholdInfo(
                                 SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
                                 lteRsrqThresholds,
+                                AccessNetworkThresholds.EUTRAN_RSRQ,
                                 AccessNetworkConstants.AccessNetworkType.EUTRAN,
                                 (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRQ) != 0));
             }
 
-            int[] lteRssnrThresholds = mCarrierConfig.getIntArray(
+            int[] lteRssnrThresholds = mCarrierConfig.getIntArray(isUsingNonTerrestrialNetwork()
+                    ? CarrierConfigManager.KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY :
                     CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY);
             if (lteRssnrThresholds != null) {
                 signalThresholdInfos.add(
-                        createSignalThresholdsInfo(
+                        validateAndCreateSignalThresholdInfo(
                                 SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
                                 lteRssnrThresholds,
+                                AccessNetworkThresholds.EUTRAN_RSSNR,
                                 AccessNetworkConstants.AccessNetworkType.EUTRAN,
                                 (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSSNR) != 0));
             }
@@ -437,9 +467,10 @@
                     CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY);
             if (nrSsrsrpThresholds != null) {
                 signalThresholdInfos.add(
-                        createSignalThresholdsInfo(
+                        validateAndCreateSignalThresholdInfo(
                                 SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
                                 nrSsrsrpThresholds,
+                                AccessNetworkThresholds.NGRAN_SSRSRP,
                                 AccessNetworkConstants.AccessNetworkType.NGRAN,
                                 (nrMeasurementEnabled & CellSignalStrengthNr.USE_SSRSRP) != 0));
             }
@@ -448,9 +479,10 @@
                     CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY);
             if (nrSsrsrqThresholds != null) {
                 signalThresholdInfos.add(
-                        createSignalThresholdsInfo(
+                        validateAndCreateSignalThresholdInfo(
                                 SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
                                 nrSsrsrqThresholds,
+                                AccessNetworkThresholds.NGRAN_SSRSRQ,
                                 AccessNetworkConstants.AccessNetworkType.NGRAN,
                                 (nrMeasurementEnabled & CellSignalStrengthNr.USE_SSRSRQ) != 0));
             }
@@ -459,9 +491,10 @@
                     CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY);
             if (nrSssinrThresholds != null) {
                 signalThresholdInfos.add(
-                        createSignalThresholdsInfo(
+                        validateAndCreateSignalThresholdInfo(
                                 SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
                                 nrSssinrThresholds,
+                                AccessNetworkThresholds.NGRAN_SSSINR,
                                 AccessNetworkConstants.AccessNetworkType.NGRAN,
                                 (nrMeasurementEnabled & CellSignalStrengthNr.USE_SSSINR) != 0));
             }
@@ -470,9 +503,10 @@
                     CarrierConfigManager.KEY_WCDMA_ECNO_THRESHOLDS_INT_ARRAY);
             if (wcdmaEcnoThresholds != null) {
                 signalThresholdInfos.add(
-                        createSignalThresholdsInfo(
+                        validateAndCreateSignalThresholdInfo(
                                 SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO,
                                 wcdmaEcnoThresholds,
+                                AccessNetworkThresholds.UTRAN_ECNO,
                                 AccessNetworkConstants.AccessNetworkType.UTRAN,
                                 false));
             }
@@ -720,20 +754,17 @@
         mSignalStrengthUpdatedTime = System.currentTimeMillis();
     }
 
-    boolean notifySignalStrength() {
-        boolean notified = false;
+    void notifySignalStrength() {
         if (!mSignalStrength.equals(mLastSignalStrength)) {
             try {
                 mSignalStrengthChangedRegistrants.notifyRegistrants();
                 mPhone.notifySignalStrength();
-                notified = true;
                 mLastSignalStrength = mSignalStrength;
             } catch (NullPointerException ex) {
-                log("updateSignalStrength() Phone already destroyed: " + ex
+                loge("updateSignalStrength() Phone already destroyed: " + ex
                         + "SignalStrength not notified");
             }
         }
-        return notified;
     }
 
     /**
@@ -1132,6 +1163,7 @@
 
         updateArfcnLists();
         updateReportingCriteria();
+        updateSignalStrength(new SignalStrength(mSignalStrength));
     }
 
     private static SignalThresholdInfo createSignalThresholdsInfo(
@@ -1145,6 +1177,45 @@
     }
 
     /**
+     * Validate the provided signal {@code thresholds} info and fall back to use the
+     * {@code defaultThresholds} and report anomaly if invalid to prevent crashing Phone.
+     */
+    private static SignalThresholdInfo validateAndCreateSignalThresholdInfo(
+            int measurementType, @NonNull int[] thresholds, @NonNull int[] defaultThresholds,
+            int ran, boolean isEnabled) {
+        SignalThresholdInfo signalThresholdInfo;
+        try {
+            signalThresholdInfo = new SignalThresholdInfo.Builder()
+                    .setSignalMeasurementType(measurementType)
+                    .setThresholds(thresholds)
+                    .setRadioAccessNetworkType(ran)
+                    .setIsEnabled(isEnabled)
+                    .build();
+        // TODO(b/295236831): only catch IAE when phone global exception handler is introduced.
+        // Although SignalThresholdInfo only throws IAE for invalid carrier configs, we catch
+        // all exception to prevent crashing phone before global exception handler is available.
+        } catch (Exception e) {
+            signalThresholdInfo = new SignalThresholdInfo.Builder()
+                    .setSignalMeasurementType(measurementType)
+                    .setThresholds(defaultThresholds)
+                    .setRadioAccessNetworkType(ran)
+                    .setIsEnabled(isEnabled)
+                    .build();
+
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString("28232bc4-78ff-447e-b597-7c054c802407"),
+                    "Invalid parameter to generate SignalThresholdInfo: "
+                            + "measurementType=" + measurementType
+                            + ", thresholds=" + Arrays.toString(thresholds)
+                            + ", RAN=" + ran
+                            + ", isEnabled=" + isEnabled
+                            + ". Replaced with default thresholds: " + Arrays.toString(
+                            defaultThresholds));
+        }
+        return signalThresholdInfo;
+    }
+
+    /**
      * dBm thresholds that correspond to changes in signal strength indications.
      */
     private static final class AccessNetworkThresholds {
@@ -1267,6 +1338,25 @@
         };
     }
 
+    private void onServiceStateChanged(ServiceState state) {
+        if (state.getState() != ServiceState.STATE_IN_SERVICE) {
+            return;
+        }
+
+        if (mNTNConnected.get() != state.isUsingNonTerrestrialNetwork()) {
+            log("onServiceStateChanged: update it to " + state.isUsingNonTerrestrialNetwork());
+            updateReportingCriteria();
+            mNTNConnected.set(state.isUsingNonTerrestrialNetwork());
+        }
+    }
+
+    private boolean isUsingNonTerrestrialNetwork() {
+        if (mPhone.getServiceState() == null) {
+            return false;
+        }
+        return mPhone.getServiceState().isUsingNonTerrestrialNetwork();
+    }
+
     private static void log(String msg) {
         if (DBG) Rlog.d(TAG, msg);
     }
diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java
index 1e1dbe5..59defc3 100644
--- a/src/java/com/android/internal/telephony/SimResponse.java
+++ b/src/java/com/android/internal/telephony/SimResponse.java
@@ -20,7 +20,6 @@
 
 import android.hardware.radio.RadioError;
 import android.hardware.radio.RadioResponseInfo;
-import android.hardware.radio.sim.CarrierRestrictions;
 import android.hardware.radio.sim.IRadioSimResponse;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.TelephonyManager;
@@ -113,31 +112,30 @@
             android.hardware.radio.sim.CarrierRestrictions carrierRestrictions,
             int multiSimPolicy) {
         RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
+        boolean carrierLockInfoSupported = mRil.getHalVersion(HAL_SERVICE_SIM).greater(
+                RIL.RADIO_HAL_VERSION_2_2);
         if (rr == null) {
             return;
         }
-        CarrierRestrictionRules ret;
-        int policy = CarrierRestrictionRules.MULTISIM_POLICY_NONE;
-        if (multiSimPolicy
-                == android.hardware.radio.sim.SimLockMultiSimPolicy.ONE_VALID_SIM_MUST_BE_PRESENT) {
-            policy = CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT;
-        }
-
+        int policy = RILUtils.convertAidlSimLockMultiSimPolicy(multiSimPolicy);
         int carrierRestrictionDefault =
                 CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
         if (!carrierRestrictions.allowedCarriersPrioritized) {
             carrierRestrictionDefault = CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED;
         }
-        ret = CarrierRestrictionRules.newBuilder()
-                .setAllowedCarriers(RILUtils.convertHalCarrierList(
-                        carrierRestrictions.allowedCarriers))
-                .setExcludedCarriers(RILUtils.convertHalCarrierList(
-                        carrierRestrictions.excludedCarriers))
-                .setDefaultCarrierRestriction(carrierRestrictionDefault)
-                .setMultiSimPolicy(policy)
-                .setCarrierRestrictionStatus(carrierRestrictions.status)
-                .build();
 
+        CarrierRestrictionRules ret = CarrierRestrictionRules.newBuilder().setAllowedCarriers(
+                RILUtils.convertHalCarrierList(
+                        carrierRestrictions.allowedCarriers)).setExcludedCarriers(
+                RILUtils.convertHalCarrierList(
+                        carrierRestrictions.excludedCarriers)).setDefaultCarrierRestriction(
+                carrierRestrictionDefault).setMultiSimPolicy(policy).setCarrierRestrictionStatus(
+                carrierRestrictions.status).setAllowedCarrierInfo(
+                RILUtils.convertAidlCarrierInfoList(
+                        carrierRestrictions.allowedCarrierInfoList)).setExcludedCarrierInfo(
+                RILUtils.convertAidlCarrierInfoList(
+                        carrierRestrictions.excludedCarrierInfoList)).setCarrierLockInfoFeature(
+                carrierLockInfoSupported).build();
         if (responseInfo.error == RadioError.NONE) {
             RadioResponse.sendMessageResponse(rr.mResult, ret);
         }
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index ecd6276..7fc499e 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -31,6 +31,8 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 
+import com.android.internal.telephony.analytics.TelephonyAnalytics;
+import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
@@ -242,6 +244,14 @@
                     if (phone != null) {
                         phone.getSmsStats().onDroppedIncomingMultipartSms(message.mIs3gpp2, rows,
                                 message.mMessageCount);
+                        TelephonyAnalytics telephonyAnalytics = phone.getTelephonyAnalytics();
+                        if (telephonyAnalytics != null) {
+                            SmsMmsAnalytics smsMmsAnalytics =
+                                    telephonyAnalytics.getSmsMmsAnalytics();
+                            if (smsMmsAnalytics != null) {
+                                smsMmsAnalytics.onDroppedIncomingMultipartSms();
+                            }
+                        }
                     }
                 }
             }
diff --git a/src/java/com/android/internal/telephony/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java
index 97161f8..da1b07a 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -18,14 +18,18 @@
 
 package com.android.internal.telephony;
 
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_MESSAGING;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
 
 import static com.android.internal.telephony.util.TelephonyUtils.checkDumpPermission;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -35,6 +39,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.TelephonyServiceManager.ServiceRegisterer;
+import android.os.UserHandle;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SmsManager;
@@ -44,6 +49,7 @@
 import android.telephony.TelephonyManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -52,6 +58,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 
@@ -62,10 +69,14 @@
     static final String LOG_TAG = "SmsController";
 
     private final Context mContext;
+    private final PackageManager mPackageManager;
+    @NonNull private final FeatureFlags mFlags;
 
     @VisibleForTesting
-    public SmsController(Context context) {
+    public SmsController(Context context, @NonNull FeatureFlags flags) {
         mContext = context;
+        mFlags = flags;
+        mPackageManager = context.getPackageManager();
         ServiceRegisterer smsServiceRegisterer = TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
                 .getSmsServiceRegisterer();
@@ -279,6 +290,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "sendTextForSubscriber");
+
         long token = Binder.clearCallingIdentity();
         SubscriptionInfo info;
         try {
@@ -396,6 +409,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "sendMultipartTextForSubscriber");
+
         // Perform FDN check
         if (isNumberBlockedByFDN(subId, destAddr, callingPackage)) {
             sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE);
@@ -462,6 +477,9 @@
     @Override
     public boolean enableCellBroadcastRangeForSubscriber(int subId, int startMessageId,
             int endMessageId, int ranType) {
+        enforceTelephonyFeatureWithException(getCallingPackage(),
+                "enableCellBroadcastRangeForSubscriber");
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.enableCellBroadcastRange(startMessageId, endMessageId, ranType);
@@ -484,6 +502,9 @@
     @Override
     public boolean disableCellBroadcastRangeForSubscriber(int subId, int startMessageId,
             int endMessageId, int ranType) {
+        enforceTelephonyFeatureWithException(getCallingPackage(),
+                "disableCellBroadcastRangeForSubscriber");
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.disableCellBroadcastRange(startMessageId, endMessageId, ranType);
@@ -496,6 +517,8 @@
 
     @Override
     public int getPremiumSmsPermission(String packageName) {
+        enforceTelephonyFeatureWithException(packageName, "getPremiumSmsPermission");
+
         return getPremiumSmsPermissionForSubscriber(getPreferredSmsSubscription(), packageName);
     }
 
@@ -513,6 +536,8 @@
 
     @Override
     public void setPremiumSmsPermission(String packageName, int permission) {
+        enforceTelephonyFeatureWithException(packageName, "setPremiumSmsPermission");
+
         setPremiumSmsPermissionForSubscriber(getPreferredSmsSubscription(), packageName,
                 permission);
     }
@@ -553,34 +578,60 @@
                     + "Suppressing activity.");
             return false;
         }
+
+        enforceTelephonyFeatureWithException(getCallingPackage(), "isSmsSimPickActivityNeeded");
+
         TelephonyManager telephonyManager =
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        List<SubscriptionInfo> subInfoList;
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            subInfoList = SubscriptionManager.from(context).getActiveSubscriptionInfoList();
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        if (mFlags.enforceSubscriptionUserFilter()) {
+            int[] activeSubIds;
+            final UserHandle user = Binder.getCallingUserHandle();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                activeSubIds = Arrays.stream(SubscriptionManagerService.getInstance()
+                        .getActiveSubIdList(true /*visibleOnly*/))
+                        .filter(sub -> SubscriptionManagerService.getInstance()
+                                .isSubscriptionAssociatedWithUser(sub, user))
+                        .toArray();
+                for (int activeSubId : activeSubIds) {
+                    // Check if the subId is associated with the caller user profile.
+                    if (activeSubId == subId) {
+                        return false;
+                    }
+                }
 
-        if (subInfoList != null) {
-            final int subInfoLength = subInfoList.size();
+                // If reached here and multiple SIMs and subs present, need sms sim pick activity.
+                return activeSubIds.length > 1 && telephonyManager.getSimCount() > 1;
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        } else {
+            List<SubscriptionInfo> subInfoList;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                subInfoList = SubscriptionManager.from(context).getActiveSubscriptionInfoList();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
 
-            for (int i = 0; i < subInfoLength; ++i) {
-                final SubscriptionInfo sir = subInfoList.get(i);
-                if (sir != null && sir.getSubscriptionId() == subId) {
-                    // The subscription id is valid, sms sim pick activity not needed
-                    return false;
+            if (subInfoList != null) {
+                final int subInfoLength = subInfoList.size();
+
+                for (int i = 0; i < subInfoLength; ++i) {
+                    final SubscriptionInfo sir = subInfoList.get(i);
+                    if (sir != null && sir.getSubscriptionId() == subId) {
+                        // The subscription id is valid, sms sim pick activity not needed
+                        return false;
+                    }
+                }
+
+                // If reached here and multiple SIMs and subs present, need sms sim pick activity
+                if (subInfoLength > 1 && telephonyManager.getSimCount() > 1) {
+                    return true;
                 }
             }
-
-            // If reached here and multiple SIMs and subs present, sms sim pick activity is needed
-            if (subInfoLength > 1 && telephonyManager.getSimCount() > 1) {
-                return true;
-            }
+            return false;
         }
-
-        return false;
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -598,6 +649,8 @@
     @Override
     public void injectSmsPduForSubscriber(
             int subId, byte[] pdu, String format, PendingIntent receivedIntent) {
+        enforceTelephonyFeatureWithException(getCallingPackage(), "injectSmsPduForSubscriber");
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.injectSmsPdu(pdu, format, receivedIntent);
@@ -624,6 +677,9 @@
         if (SubscriptionManager.isValidSubscriptionId(defaultSubId)) {
             return defaultSubId;
         }
+
+        enforceTelephonyFeatureWithException(getCallingPackage(), "getPreferredSmsSubscription");
+
         // No default, if there is only one sub active, choose that as the "preferred" sub id.
         long token = Binder.clearCallingIdentity();
         try {
@@ -692,6 +748,9 @@
 
     @Override
     public Bundle getCarrierConfigValuesForSubscriber(int subId) {
+        enforceTelephonyFeatureWithException(getCallingPackage(),
+                "getCarrierConfigValuesForSubscriber");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             final CarrierConfigManager configManager =
@@ -820,6 +879,10 @@
         if (callingPkg == null) {
             callingPkg = getCallingPackage();
         }
+
+        enforceTelephonyFeatureWithException(callingPkg,
+                "createAppSpecificSmsTokenWithPackageInfo");
+
         return getPhone(subId).getAppSmsManager().createAppSpecificSmsTokenWithPackageInfo(
                 subId, callingPkg, prefixes, intent);
     }
@@ -829,6 +892,9 @@
         if (callingPkg == null) {
             callingPkg = getCallingPackage();
         }
+
+        enforceTelephonyFeatureWithException(callingPkg, "createAppSpecificSmsToken");
+
         return getPhone(subId).getAppSmsManager().createAppSpecificSmsToken(callingPkg, intent);
     }
 
@@ -944,6 +1010,10 @@
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+
+        enforceTelephonyFeatureWithException(callingPackage,
+                "getSmscAddressFromIccEfForSubscriber");
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.getSmscAddressFromIccEf(callingPackage);
@@ -960,6 +1030,10 @@
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+
+        enforceTelephonyFeatureWithException(callingPackage,
+                "setSmscAddressOnIccEfForSubscriber");
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.setSmscAddressOnIccEf(callingPackage, smsc);
@@ -1035,6 +1109,9 @@
      */
     @Override
     public int getSmsCapacityOnIccForSubscriber(int subId) {
+        enforceTelephonyFeatureWithException(getCallingPackage(),
+                "getSmsCapacityOnIccForSubscriber");
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
 
         if (iccSmsIntMgr != null ) {
@@ -1053,6 +1130,9 @@
      */
     @Override
     public boolean resetAllCellBroadcastRanges(int subId) {
+        enforceTelephonyFeatureWithException(getCallingPackage(),
+                "resetAllCellBroadcastRanges");
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.resetAllCellBroadcastRanges();
@@ -1114,4 +1194,43 @@
         // Check if smscAddr is present in FDN list
         return FdnUtils.isNumberBlockedByFDN(phoneId, smscAddr, defaultCountryIso);
     }
+
+    /**
+     * Gets the message size of WAP from the cache.
+     *
+     * @param locationUrl the location to use as a key for looking up the size in the cache.
+     * The locationUrl may or may not have the transactionId appended to the url.
+     *
+     * @return long representing the message size
+     * @throws java.util.NoSuchElementException if the WAP push doesn't exist in the cache
+     * @throws IllegalArgumentException if the locationUrl is empty
+     */
+    @Override
+    public long getWapMessageSize(@NonNull String locationUrl) {
+        byte[] bytes = locationUrl.getBytes(StandardCharsets.ISO_8859_1);
+        return WapPushCache.getWapMessageSize(bytes);
+    }
+
+    /**
+     * Make sure the device has required telephony feature
+     *
+     * @throws UnsupportedOperationException if the device does not have required telephony feature
+     */
+    private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+            @NonNull String methodName) {
+        if (callingPackage == null || mPackageManager == null) {
+            return;
+        }
+
+        if (!mFlags.enforceTelephonyFeatureMappingForPublicApis()
+                || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+                Binder.getCallingUserHandle())) {
+            return;
+        }
+
+        if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY_MESSAGING)) {
+            throw new UnsupportedOperationException(
+                    methodName + " is unsupported without " + FEATURE_TELEPHONY_MESSAGING);
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index d2dfcac..8795840 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -45,16 +45,20 @@
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
 
 import com.android.ims.ImsManager;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
 import com.android.internal.telephony.cdma.CdmaSMSDispatcher;
 import com.android.internal.telephony.domainselection.DomainSelectionConnection;
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.domainselection.EmergencySmsDomainSelectionConnection;
 import com.android.internal.telephony.domainselection.SmsDomainSelectionConnection;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
 import com.android.telephony.Rlog;
@@ -62,8 +66,10 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -94,6 +100,15 @@
     /** InboundSmsHandler exited WaitingState */
     protected static final int EVENT_SMS_HANDLER_EXITING_WAITING_STATE = 17;
 
+    /** Called when SMS should be sent using AP domain selection. */
+    private static final int EVENT_SEND_SMS_USING_DOMAIN_SELECTION = 18;
+
+    /** Called when SMS is completely sent using AP domain selection regardless of the result. */
+    private static final int EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION = 19;
+
+    /** Called when AP domain selection is abnormally terminated. */
+    private static final int EVENT_DOMAIN_SELECTION_TERMINATED_ABNORMALLY = 20;
+
     /** Delete any partial message segments after being IN_SERVICE for 1 day. */
     private static final long PARTIAL_SEGMENT_WAIT_DURATION = (long) (60 * 60 * 1000) * 24;
     /** Constant for invalid time */
@@ -117,6 +132,7 @@
     private final SmsUsageMonitor mUsageMonitor;
     private final CommandsInterface mCi;
     private final Context mContext;
+    private final @NonNull FeatureFlags mFeatureFlags;
 
     /** true if IMS is registered and sms is supported, false otherwise.*/
     private boolean mIms = false;
@@ -183,7 +199,7 @@
             };
 
     /** Stores the sending SMS information for a pending request. */
-    private class PendingRequest {
+    private static class PendingRequest {
         public static final int TYPE_DATA = 1;
         public static final int TYPE_TEXT = 2;
         public static final int TYPE_MULTIPART_TEXT = 3;
@@ -310,13 +326,18 @@
 
         @Override
         public void onSelectionTerminated(@DisconnectCauses int cause) {
-            notifyDomainSelectionTerminated(this);
+            logd("onSelectionTerminated: emergency=" + mEmergency + ", cause=" + cause);
+            // This callback is invoked by another thread, so this operation is posted and handled
+            // through the execution flow of SmsDispatchersController.
+            SmsDispatchersController.this.sendMessage(
+                    obtainMessage(EVENT_DOMAIN_SELECTION_TERMINATED_ABNORMALLY, this));
         }
     }
 
     /** Manages the domain selection connections: MO SMS or emergency SMS. */
     private DomainSelectionConnectionHolder mDscHolder;
     private DomainSelectionConnectionHolder mEmergencyDscHolder;
+    private EmergencyStateTracker mEmergencyStateTracker;
 
     /**
      * Puts a delivery pending tracker to the map based on the format.
@@ -332,13 +353,13 @@
     }
 
     public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor,
-            SmsUsageMonitor usageMonitor) {
-        this(phone, storageMonitor, usageMonitor, phone.getLooper());
+            SmsUsageMonitor usageMonitor, @NonNull FeatureFlags featureFlags) {
+        this(phone, storageMonitor, usageMonitor, phone.getLooper(), featureFlags);
     }
 
     @VisibleForTesting
     public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor,
-            SmsUsageMonitor usageMonitor, Looper looper) {
+            SmsUsageMonitor usageMonitor, Looper looper, @NonNull FeatureFlags featureFlags) {
         super(looper);
 
         Rlog.d(TAG, "SmsDispatchersController created");
@@ -346,6 +367,7 @@
         mContext = phone.getContext();
         mUsageMonitor = usageMonitor;
         mCi = phone.mCi;
+        mFeatureFlags = featureFlags;
         mPhone = phone;
 
         // Create dispatchers, inbound SMS handlers and
@@ -447,7 +469,36 @@
                 mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
                 resetPartialSegmentWaitTimer();
                 break;
-
+            case EVENT_SEND_SMS_USING_DOMAIN_SELECTION: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                DomainSelectionConnectionHolder holder =
+                        (DomainSelectionConnectionHolder) args.arg1;
+                PendingRequest request = (PendingRequest) args.arg2;
+                String logTag = (String) args.arg3;
+                try {
+                    handleSendSmsUsingDomainSelection(holder, request, logTag);
+                } finally {
+                    args.recycle();
+                }
+                break;
+            }
+            case EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                String destAddr = (String) args.arg1;
+                Long messageId = (Long) args.arg2;
+                Boolean success = (Boolean) args.arg3;
+                try {
+                    handleSmsSentCompletedUsingDomainSelection(destAddr, messageId, success);
+                } finally {
+                    args.recycle();
+                }
+                break;
+            }
+            case EVENT_DOMAIN_SELECTION_TERMINATED_ABNORMALLY: {
+                handleDomainSelectionTerminatedAbnormally(
+                        (DomainSelectionConnectionHolder) msg.obj);
+                break;
+            }
             default:
                 if (isCdmaMo()) {
                     mCdmaDispatcher.handleMessage(msg);
@@ -701,7 +752,7 @@
         boolean retryUsingImsService = false;
 
         if (!tracker.mUsesImsServiceForIms) {
-            if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) {
+            if (isSmsDomainSelectionEnabled()) {
                 DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false);
 
                 // If the DomainSelectionConnection is not available,
@@ -756,6 +807,8 @@
                 // should never come here...
                 Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!");
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
+                notifySmsSentFailedToEmergencyStateTracker(
+                        tracker.mDestAddress, tracker.mMessageId);
                 return;
             }
             String scAddr = (String) map.get("scAddr");
@@ -763,6 +816,8 @@
             if (destAddr == null) {
                 Rlog.e(TAG, "sendRetrySms failed due to null destAddr");
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
+                notifySmsSentFailedToEmergencyStateTracker(
+                        tracker.mDestAddress, tracker.mMessageId);
                 return;
             }
 
@@ -803,6 +858,8 @@
                         + "scAddr: %s, "
                         + "destPort: %s", scAddr, map.get("destPort")));
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
+                notifySmsSentFailedToEmergencyStateTracker(
+                        tracker.mDestAddress, tracker.mMessageId);
                 return;
             }
             // replace old smsc and pdu with newly encoded ones
@@ -889,6 +946,16 @@
     }
 
     /**
+     * Checks whether the SMS domain selection is enabled or not.
+     *
+     * @return {@code true} if the SMS domain selection is enabled, {@code false} otherwise.
+     */
+    private boolean isSmsDomainSelectionEnabled() {
+        return mFeatureFlags.smsDomainSelectionEnabled()
+                && mDomainSelectionResolverProxy.isDomainSelectionSupported();
+    }
+
+    /**
      * Determines whether or not to use CDMA format for MO SMS when the domain selection uses.
      * If the domain is {@link NetworkRegistrationInfo#DOMAIN_PS}, then format is based on
      * IMS SMS format, otherwise format is based on current phone type.
@@ -929,7 +996,6 @@
     private DomainSelectionConnectionHolder getDomainSelectionConnection(boolean emergency) {
         DomainSelectionConnectionHolder holder = getDomainSelectionConnectionHolder(emergency);
         DomainSelectionConnection connection = (holder != null) ? holder.getConnection() : null;
-        boolean created = false;
 
         if (connection == null) {
             connection = mDomainSelectionResolverProxy.getDomainSelectionConnection(
@@ -940,8 +1006,6 @@
                 // Use the legacy architecture.
                 return null;
             }
-
-            created = true;
         }
 
         if (holder == null) {
@@ -965,6 +1029,7 @@
      * @param holder The {@link DomainSelectionConnectionHolder} that contains the
      *               {@link DomainSelectionConnection} and its related information.
      */
+    @SuppressWarnings("FutureReturnValueIgnored")
     private void requestDomainSelection(@NonNull DomainSelectionConnectionHolder holder) {
         DomainSelectionService.SelectionAttributes attr =
                 new DomainSelectionService.SelectionAttributes.Builder(mPhone.getPhoneId(),
@@ -1001,16 +1066,14 @@
     }
 
     /**
-     * Sends a SMS after selecting the domain via the domain selection service.
+     * Requests the domain selection for MO SMS.
      *
      * @param holder The {@link DomainSelectionConnectionHolder} that contains the
      *               {@link DomainSelectionConnection} and its related information.
-     * @param request The {@link PendingRequest} that stores the SMS request
-     *                (data, text, multipart text) to be sent.
-     * @param logTag The log tag to display which method called this method.
+     * @param logTag The log string.
      */
-    private void sendSmsUsingDomainSelection(@NonNull DomainSelectionConnectionHolder holder,
-            @NonNull PendingRequest request, @NonNull String logTag) {
+    private void requestDomainSelection(@NonNull DomainSelectionConnectionHolder holder,
+            @NonNull PendingRequest request, String logTag) {
         boolean isDomainSelectionRequested = holder.isDomainSelectionRequested();
         // The domain selection is in progress so waits for the result of
         // the domain selection by adding this request to the pending list.
@@ -1025,6 +1088,120 @@
     }
 
     /**
+     * Handles an event for sending a SMS after selecting the domain via the domain selection
+     * service.
+     *
+     * @param holder The {@link DomainSelectionConnectionHolder} that contains the
+     *               {@link DomainSelectionConnection} and its related information.
+     * @param request The {@link PendingRequest} that stores the SMS request
+     *                (data, text, multipart text) to be sent.
+     * @param logTag The log tag to display which method called this method.
+     */
+    @SuppressWarnings("FutureReturnValueIgnored")
+    private void handleSendSmsUsingDomainSelection(@NonNull DomainSelectionConnectionHolder holder,
+            @NonNull PendingRequest request, @NonNull String logTag) {
+        if (holder.isEmergency()) {
+            if (mEmergencyStateTracker == null) {
+                mEmergencyStateTracker = EmergencyStateTracker.getInstance();
+            }
+
+            CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencySms(mPhone,
+                    String.valueOf(request.messageId),
+                    isTestEmergencyNumber(request.destAddr));
+            future.thenAccept((result) -> {
+                logi("startEmergencySms(" + logTag + "): messageId=" + request.messageId
+                        + ", result=" + result);
+                // An emergency SMS should be proceeded regardless of the result of the
+                // EmergencyStateTracker.
+                // So the domain selection request should be invoked without checking the result.
+                requestDomainSelection(holder, request, logTag);
+            });
+        } else {
+            requestDomainSelection(holder, request, logTag);
+        }
+    }
+
+    /**
+     * Sends a SMS after selecting the domain via the domain selection service.
+     *
+     * @param holder The {@link DomainSelectionConnectionHolder} that contains the
+     *               {@link DomainSelectionConnection} and its related information.
+     * @param request The {@link PendingRequest} that stores the SMS request
+     *                (data, text, multipart text) to be sent.
+     * @param logTag The log tag to display which method called this method.
+     */
+    private void sendSmsUsingDomainSelection(@NonNull DomainSelectionConnectionHolder holder,
+            @NonNull PendingRequest request, @NonNull String logTag) {
+        // Run on main thread for interworking with EmergencyStateTracker
+        // and adding the pending request.
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = holder;
+        args.arg2 = request;
+        args.arg3 = logTag;
+        sendMessage(obtainMessage(EVENT_SEND_SMS_USING_DOMAIN_SELECTION, args));
+    }
+
+    /**
+     * Called when sending MO SMS is complete regardless of the sent result.
+     *
+     * @param destAddr The destination address for SMS.
+     * @param messageId The message id for SMS.
+     * @param success A flag specifying whether MO SMS is successfully sent or not.
+     */
+    private void handleSmsSentCompletedUsingDomainSelection(@NonNull String destAddr,
+            long messageId, boolean success) {
+        if (mEmergencyStateTracker != null) {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            if (tm.isEmergencyNumber(destAddr)) {
+                mEmergencyStateTracker.endSms(String.valueOf(messageId), success);
+            }
+        }
+    }
+
+    /**
+     * Called when MO SMS is successfully sent.
+     */
+    protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId) {
+        if (isSmsDomainSelectionEnabled()) {
+            // Run on main thread for interworking with EmergencyStateTracker.
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = destAddr;
+            args.arg2 = Long.valueOf(messageId);
+            args.arg3 = Boolean.TRUE;
+            sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args));
+        }
+    }
+
+    /**
+     * Called when sending MO SMS is failed.
+     */
+    protected void notifySmsSentFailedToEmergencyStateTracker(@NonNull String destAddr,
+            long messageId) {
+        if (isSmsDomainSelectionEnabled()) {
+            // Run on main thread for interworking with EmergencyStateTracker.
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = destAddr;
+            args.arg2 = Long.valueOf(messageId);
+            args.arg3 = Boolean.FALSE;
+            sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args));
+        }
+    }
+
+    private boolean isTestEmergencyNumber(String number) {
+        try {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            Map<Integer, List<EmergencyNumber>> eMap = tm.getEmergencyNumberList();
+            return eMap.values().stream().flatMap(Collection::stream).anyMatch(eNumber ->
+                    eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)
+                    && number.equals(eNumber.getNumber()));
+        } catch (IllegalStateException ise) {
+            return false;
+        } catch (RuntimeException r) {
+            return false;
+        }
+    }
+
+    /**
      * Finishes the domain selection for MO SMS.
      *
      * @param holder The {@link DomainSelectionConnectionHolder} object that is being finished.
@@ -1054,21 +1231,16 @@
     }
 
     /**
-     * Notifies the application that MO SMS is not sent by the error of domain selection.
+     * Called when MO SMS is not sent by the error of domain selection.
      *
      * @param holder The {@link DomainSelectionConnectionHolder} object that is being terminated.
      */
-    private void notifyDomainSelectionTerminated(@NonNull DomainSelectionConnectionHolder holder) {
-        final List<PendingRequest> pendingRequests = holder.getPendingRequests();
-
-        logd("notifyDomainSelectionTerminated: pendingRequests=" + pendingRequests.size());
-
-        for (PendingRequest r : pendingRequests) {
-            triggerSentIntentForFailure(r.sentIntents);
-        }
-
+    private void handleDomainSelectionTerminatedAbnormally(
+            @NonNull DomainSelectionConnectionHolder holder) {
+        logd("handleDomainSelectionTerminatedAbnormally: pendingRequests="
+                + holder.getPendingRequests().size());
+        sendAllPendingRequests(holder, NetworkRegistrationInfo.DOMAIN_UNKNOWN);
         holder.setConnection(null);
-        holder.clearAllRequests();
     }
 
     /**
@@ -1089,12 +1261,36 @@
                     + ", size=" + pendingRequests.size());
         }
 
+        // When the domain selection request is failed, SMS should be fallback
+        // to the legacy implementation.
+        boolean wasDomainUnknown = false;
+
+        if (domain == NetworkRegistrationInfo.DOMAIN_UNKNOWN) {
+            logd("sendAllPendingRequests: fallback - imsAvailable="
+                    + mImsSmsDispatcher.isAvailable());
+
+            wasDomainUnknown = true;
+
+            if (mImsSmsDispatcher.isAvailable()) {
+                domain = NetworkRegistrationInfo.DOMAIN_PS;
+            } else {
+                domain = NetworkRegistrationInfo.DOMAIN_CS;
+            }
+        }
+
         for (PendingRequest r : pendingRequests) {
             switch (r.type) {
                 case PendingRequest.TYPE_DATA:
                     sendData(domain, r);
                     break;
                 case PendingRequest.TYPE_TEXT:
+                    // When the domain selection request is failed, emergency SMS should be fallback
+                    // to the legacy implementation.
+                    if (wasDomainUnknown
+                            && domain != NetworkRegistrationInfo.DOMAIN_PS
+                            && mImsSmsDispatcher.isEmergencySmsSupport(r.destAddr)) {
+                        domain = NetworkRegistrationInfo.DOMAIN_PS;
+                    }
                     sendText(domain, r);
                     break;
                 case PendingRequest.TYPE_MULTIPART_TEXT:
@@ -1308,7 +1504,7 @@
             scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPackage);
         }
 
-        if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) {
+        if (isSmsDomainSelectionEnabled()) {
             DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false);
 
             // If the DomainSelectionConnection is not available,
@@ -1547,7 +1743,7 @@
             scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg);
         }
 
-        if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) {
+        if (isSmsDomainSelectionEnabled()) {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
             boolean isEmergency = tm.isEmergencyNumber(destAddr);
             DomainSelectionConnectionHolder holder = getDomainSelectionConnection(isEmergency);
@@ -1696,7 +1892,7 @@
             scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg);
         }
 
-        if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) {
+        if (isSmsDomainSelectionEnabled()) {
             DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false);
 
             // If the DomainSelectionConnection is not available,
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index f7f24a2..f5aa074 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -40,11 +40,16 @@
 import com.android.internal.telephony.data.LinkBandwidthEstimator;
 import com.android.internal.telephony.data.PhoneSwitcher;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsNrSaModeHandler;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl;
+import com.android.internal.telephony.security.CellularIdentifierDisclosureNotifier;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource;
+import com.android.internal.telephony.security.NullCipherNotifier;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccProfile;
@@ -274,8 +279,14 @@
         return sInstance;
     }
 
-    public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone) {
-        return new GsmCdmaCallTracker(phone);
+    /**
+     * Create a new GsmCdmaCallTracker
+     * @param phone GsmCdmaPhone
+     * @param featureFlags Telephony feature flag
+     */
+    public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone,
+            @NonNull FeatureFlags featureFlags) {
+        return new GsmCdmaCallTracker(phone, featureFlags);
     }
 
     public SmsStorageMonitor makeSmsStorageMonitor(Phone phone) {
@@ -286,8 +297,9 @@
         return new SmsUsageMonitor(context);
     }
 
-    public ServiceStateTracker makeServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci) {
-        return new ServiceStateTracker(phone, ci);
+    public ServiceStateTracker makeServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci,
+            @NonNull FeatureFlags featureFlags) {
+        return new ServiceStateTracker(phone, ci, featureFlags);
     }
 
     /**
@@ -326,16 +338,21 @@
         return new IccPhoneBookInterfaceManager(phone);
     }
 
-    public IccSmsInterfaceManager makeIccSmsInterfaceManager(Phone phone) {
-        return new IccSmsInterfaceManager(phone);
+    /**
+     * Returns a new {@link IccSmsInterfaceManager} instance.
+     */
+    public IccSmsInterfaceManager makeIccSmsInterfaceManager(Phone phone,
+            @NonNull FeatureFlags featureFlags) {
+        return new IccSmsInterfaceManager(phone, featureFlags);
     }
 
     /**
      * Create a new UiccProfile object.
      */
     public UiccProfile makeUiccProfile(Context context, CommandsInterface ci, IccCardStatus ics,
-                                       int phoneId, UiccCard uiccCard, Object lock) {
-        return new UiccProfile(context, ci, ics, phoneId, uiccCard, lock);
+                                       int phoneId, UiccCard uiccCard, Object lock,
+            @NonNull FeatureFlags flags) {
+        return new UiccProfile(context, ci, ics, phoneId, uiccCard, lock, flags);
     }
 
     public EriManager makeEriManager(Phone phone, int eriFileSource) {
@@ -377,8 +394,27 @@
         return new InboundSmsTracker(context, cursor, isCurrentFormat3gpp2);
     }
 
+    /**
+     * Create an ImsPhoneCallTracker.
+     *
+     * @param imsPhone imsphone
+     * @return ImsPhoneCallTracker newly created ImsPhoneCallTracker
+     * @deprecated Use {@link #makeImsPhoneCallTracker(ImsPhone, FeatureFlags)} instead
+     */
     public ImsPhoneCallTracker makeImsPhoneCallTracker(ImsPhone imsPhone) {
-        return new ImsPhoneCallTracker(imsPhone, ImsManager::getConnector);
+        return makeImsPhoneCallTracker(imsPhone, new FeatureFlagsImpl());
+    }
+
+    /**
+     * Create a ims phone call tracker.
+     *
+     * @param imsPhone imsphone
+     * @param featureFlags feature flags
+     * @return ImsPhoneCallTracker newly created ImsPhoneCallTracker
+     */
+    public ImsPhoneCallTracker makeImsPhoneCallTracker(ImsPhone imsPhone,
+                                                       @NonNull FeatureFlags featureFlags) {
+        return new ImsPhoneCallTracker(imsPhone, ImsManager::getConnector, featureFlags);
     }
 
     public ImsExternalCallTracker makeImsExternalCallTracker(ImsPhone imsPhone) {
@@ -401,8 +437,12 @@
         return new AppSmsManager(context);
     }
 
-    public DeviceStateMonitor makeDeviceStateMonitor(Phone phone) {
-        return new DeviceStateMonitor(phone);
+    /**
+     * Create a DeviceStateMonitor.
+     */
+    public DeviceStateMonitor makeDeviceStateMonitor(Phone phone,
+            @NonNull FeatureFlags featureFlags) {
+        return new DeviceStateMonitor(phone, featureFlags);
     }
 
     /**
@@ -411,9 +451,23 @@
      * @param phone The phone instance
      * @param looper Looper for the handler.
      * @return The access networks manager
+     * @deprecated {@link #makeAccessNetworksManager(Phone, Looper, FeatureFlags)} instead
      */
     public AccessNetworksManager makeAccessNetworksManager(Phone phone, Looper looper) {
-        return new AccessNetworksManager(phone, looper);
+        return new AccessNetworksManager(phone, looper, new FeatureFlagsImpl());
+    }
+
+    /**
+     * Make access networks manager
+     *
+     * @param phone The phone instance
+     * @param looper Looper for the handler.
+     * @param featureFlags feature flags.
+     * @return The access networks manager
+     */
+    public AccessNetworksManager makeAccessNetworksManager(Phone phone, Looper looper,
+            @NonNull FeatureFlags featureFlags) {
+        return new AccessNetworksManager(phone, looper, featureFlags);
     }
 
     public CdmaSubscriptionSourceManager
@@ -423,27 +477,28 @@
     }
 
     public LocaleTracker makeLocaleTracker(Phone phone, NitzStateMachine nitzStateMachine,
-                                           Looper looper) {
-        return new LocaleTracker(phone, nitzStateMachine, looper);
+                                           Looper looper, @NonNull FeatureFlags featureFlags) {
+        return new LocaleTracker(phone, nitzStateMachine, looper, featureFlags);
     }
 
     public Phone makePhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
             int phoneId, int precisePhoneType,
-            TelephonyComponentFactory telephonyComponentFactory) {
+            TelephonyComponentFactory telephonyComponentFactory,
+            @NonNull FeatureFlags featureFlags) {
         return new GsmCdmaPhone(context, ci, notifier, phoneId, precisePhoneType,
-                telephonyComponentFactory);
+                telephonyComponentFactory, featureFlags);
     }
 
     public PhoneSwitcher makePhoneSwitcher(int maxDataAttachModemCount, Context context,
-            Looper looper) {
-        return PhoneSwitcher.make(maxDataAttachModemCount, context, looper);
+            Looper looper, @NonNull FeatureFlags featureFlags) {
+        return PhoneSwitcher.make(maxDataAttachModemCount, context, looper, featureFlags);
     }
 
     /**
      * Create a new DisplayInfoController.
      */
-    public DisplayInfoController makeDisplayInfoController(Phone phone) {
-        return new DisplayInfoController(phone);
+    public DisplayInfoController makeDisplayInfoController(Phone phone, FeatureFlags featureFlags) {
+        return new DisplayInfoController(phone, featureFlags);
     }
 
     /**
@@ -476,10 +531,12 @@
      *
      * @param phone The phone object
      * @param looper The looper for event handling
+     * @param featureFlags The feature flag.
      * @return The data network controller instance
      */
-    public DataNetworkController makeDataNetworkController(Phone phone, Looper looper) {
-        return new DataNetworkController(phone, looper);
+    public DataNetworkController makeDataNetworkController(Phone phone, Looper looper,
+            @NonNull FeatureFlags featureFlags) {
+        return new DataNetworkController(phone, looper, featureFlags);
     }
 
     /**
@@ -490,15 +547,17 @@
      * @param dataServiceManager Data service manager instance.
      * @param looper The looper to be used by the handler. Currently the handler thread is the phone
      * process's main thread.
+     * @param featureFlags Feature flags controlling which feature is enabled.     *
      * @param callback Callback for passing events back to data network controller.
      * @return The data profile manager instance.
      */
     public @NonNull DataProfileManager makeDataProfileManager(@NonNull Phone phone,
             @NonNull DataNetworkController dataNetworkController,
             @NonNull DataServiceManager dataServiceManager, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
             @NonNull DataProfileManager.DataProfileManagerCallback callback) {
         return new DataProfileManager(phone, dataNetworkController, dataServiceManager, looper,
-                callback);
+                featureFlags, callback);
     }
 
     /**
@@ -516,4 +575,21 @@
             @NonNull DataSettingsManager.DataSettingsManagerCallback callback) {
         return new DataSettingsManager(phone, dataNetworkController, looper, callback);
     }
+
+    /** Create CellularNetworkSecuritySafetySource. */
+    public CellularNetworkSecuritySafetySource makeCellularNetworkSecuritySafetySource(
+            Context context) {
+        return CellularNetworkSecuritySafetySource.getInstance(context);
+    }
+
+    /** Create CellularIdentifierDisclosureNotifier. */
+    public CellularIdentifierDisclosureNotifier makeIdentifierDisclosureNotifier(
+            CellularNetworkSecuritySafetySource safetySource) {
+        return CellularIdentifierDisclosureNotifier.getInstance(safetySource);
+    }
+
+    /** Create NullCipherNotifier. */
+    public NullCipherNotifier makeNullCipherNotifier() {
+        return NullCipherNotifier.getInstance();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/TelephonyCountryDetector.java b/src/java/com/android/internal/telephony/TelephonyCountryDetector.java
new file mode 100644
index 0000000..56e8b46
--- /dev/null
+++ b/src/java/com/android/internal/telephony/TelephonyCountryDetector.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2023 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;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.Address;
+import android.location.Geocoder;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.telephony.Rlog;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class is used to detect the country where the device is.
+ *
+ * {@link LocaleTracker} also tracks country of a device based on the information provided by
+ * network operators. However, it won't work when a device is out of service. In such cases and if
+ * Wi-Fi is available, {@link Geocoder} can be used to query the country for the current location of
+ * the device. {@link TelephonyCountryDetector} uses both {@link LocaleTracker} and {@link Geocoder}
+ * to track country of a device.
+ */
+public class TelephonyCountryDetector extends Handler {
+    private static final String TAG = "TelephonyCountryDetector";
+    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+    private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem";
+    private static final boolean DEBUG = !"user".equals(Build.TYPE);
+    private static final int EVENT_LOCATION_CHANGED = 1;
+    private static final int EVENT_LOCATION_COUNTRY_CODE_CHANGED = 2;
+    private static final int EVENT_NETWORK_COUNTRY_CODE_CHANGED = 3;
+    private static final int EVENT_WIFI_CONNECTIVITY_STATE_CHANGED = 4;
+    private static final int EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET = 5;
+
+    // Wait 12 hours between location updates
+    private static final long TIME_BETWEEN_LOCATION_UPDATES_MILLIS = TimeUnit.HOURS.toMillis(12);
+    // Minimum distance before a location update is triggered, in meters. We don't need this to be
+    // too exact because all we care about is in what country the device is.
+    private static final float DISTANCE_BETWEEN_LOCATION_UPDATES_METERS = 2000;
+    protected static final long WAIT_FOR_LOCATION_UPDATE_REQUEST_QUOTA_RESET_TIMEOUT_MILLIS =
+            TimeUnit.MINUTES.toMillis(30);
+
+    private static TelephonyCountryDetector sInstance;
+
+    @NonNull private final Geocoder mGeocoder;
+    @NonNull private final LocationManager mLocationManager;
+    @NonNull private final ConnectivityManager mConnectivityManager;
+    @NonNull private final Object mLock = new Object();
+    @NonNull
+    @GuardedBy("mLock")
+    private final Map<Integer, NetworkCountryCodeInfo> mNetworkCountryCodeInfoPerPhone =
+            new HashMap<>();
+    @GuardedBy("mLock")
+    private String mLocationCountryCode = null;
+    /** This should be used by CTS only */
+    @GuardedBy("mLock")
+    private String mOverriddenLocationCountryCode = null;
+    @GuardedBy("mLock")
+    private boolean mIsLocationUpdateRequested = false;
+    @GuardedBy("mLock")
+    private long mLocationCountryCodeUpdatedTimestampNanos = 0;
+    /** This should be used by CTS only */
+    @GuardedBy("mLock")
+    private long mOverriddenLocationCountryCodeUpdatedTimestampNanos = 0;
+    @GuardedBy("mLock")
+    private List<String> mOverriddenCurrentNetworkCountryCodes = null;
+    @GuardedBy("mLock")
+    private Map<String, Long> mOverriddenCachedNetworkCountryCodes = new HashMap<>();
+    @GuardedBy("mLock")
+    private boolean mIsCountryCodesOverridden = false;
+    @NonNull private final LocationListener mLocationListener = new LocationListener() {
+        @Override
+        public void onLocationChanged(Location location) {
+            logd("onLocationChanged: " + (location != null));
+            if (location != null) {
+                sendRequestAsync(EVENT_LOCATION_CHANGED, location);
+            }
+        }
+
+        @Override
+        public void onProviderDisabled(String provider) {
+            logd("onProviderDisabled: provider=" + provider);
+        }
+
+        @Override
+        public void onProviderEnabled(String provider) {
+            logd("onProviderEnabled: provider=" + provider);
+        }
+
+        @Override
+        public void onStatusChanged(String provider, int status, Bundle extras) {
+            logd("onStatusChanged: provider=" + provider + ", status=" + status
+                    + ", extras=" + extras);
+        }
+    };
+
+    private class TelephonyGeocodeListener implements Geocoder.GeocodeListener {
+        private long mLocationUpdatedTime;
+        TelephonyGeocodeListener(long locationUpdatedTime) {
+            mLocationUpdatedTime = locationUpdatedTime;
+        }
+
+        @Override
+        public void onGeocode(List<Address> addresses) {
+            if (addresses != null && !addresses.isEmpty()) {
+                logd("onGeocode: addresses is available");
+                String countryCode = addresses.get(0).getCountryCode();
+                sendRequestAsync(EVENT_LOCATION_COUNTRY_CODE_CHANGED,
+                        new Pair<>(countryCode, mLocationUpdatedTime));
+            } else {
+                logd("onGeocode: addresses is not available");
+            }
+        }
+
+        @Override
+        public void onError(String errorMessage) {
+            loge("GeocodeListener.onError=" + errorMessage);
+        }
+    }
+
+    /**
+     * Container class to store country code per Phone.
+     */
+    private static class NetworkCountryCodeInfo {
+        public int phoneId;
+        public String countryCode;
+        public long timestamp;
+
+        @Override
+        public String toString() {
+            return "NetworkCountryCodeInfo[phoneId: " + phoneId
+                    + ", countryCode: " + countryCode
+                    + ", timestamp: " + timestamp + "]";
+        }
+    }
+
+    /**
+     * Create the singleton instance of {@link TelephonyCountryDetector}.
+     *
+     * @param looper The looper to run the {@link TelephonyCountryDetector} instance.
+     * @param context The context used by the instance.
+     * @param locationManager The LocationManager instance.
+     * @param connectivityManager The ConnectivityManager instance.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected TelephonyCountryDetector(@NonNull Looper looper, @NonNull Context context,
+            @NonNull LocationManager locationManager,
+            @NonNull ConnectivityManager connectivityManager) {
+        super(looper);
+        mLocationManager = locationManager;
+        mGeocoder = new Geocoder(context);
+        mConnectivityManager = connectivityManager;
+        initialize();
+    }
+
+    /** @return the singleton instance of the {@link TelephonyCountryDetector} */
+    public static synchronized TelephonyCountryDetector getInstance(@NonNull Context context) {
+        if (sInstance == null) {
+            HandlerThread handlerThread = new HandlerThread("TelephonyCountryDetector");
+            handlerThread.start();
+            sInstance = new TelephonyCountryDetector(handlerThread.getLooper(), context,
+                    context.getSystemService(LocationManager.class),
+                    context.getSystemService(ConnectivityManager.class));
+        }
+        return sInstance;
+    }
+
+    /**
+     * @return The list of current network country ISOs if available, an empty list otherwise.
+     */
+    @NonNull public List<String> getCurrentNetworkCountryIso() {
+        synchronized (mLock) {
+            if (mIsCountryCodesOverridden) {
+                logd("mOverriddenCurrentNetworkCountryCodes="
+                        + String.join(", ", mOverriddenCurrentNetworkCountryCodes));
+                return mOverriddenCurrentNetworkCountryCodes;
+            }
+        }
+
+        List<String> result = new ArrayList<>();
+        for (Phone phone : PhoneFactory.getPhones()) {
+            String countryIso = getNetworkCountryIsoForPhone(phone);
+            if (isValid(countryIso)) {
+                String countryIsoInUpperCase = countryIso.toUpperCase(Locale.US);
+                if (!result.contains(countryIsoInUpperCase)) {
+                    result.add(countryIsoInUpperCase);
+                }
+            } else {
+                logd("getCurrentNetworkCountryIso: invalid countryIso=" + countryIso
+                        + " for phoneId=" + phone.getPhoneId() + ", subId=" + phone.getSubId());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @return The cached location country code and its updated timestamp.
+     */
+    @NonNull public Pair<String, Long> getCachedLocationCountryIsoInfo() {
+        synchronized (mLock) {
+            if (mIsCountryCodesOverridden) {
+                logd("mOverriddenLocationCountryCode=" + mOverriddenLocationCountryCode
+                        + " will be used");
+                return new Pair<>(mOverriddenLocationCountryCode,
+                        mOverriddenLocationCountryCodeUpdatedTimestampNanos);
+            }
+            return new Pair<>(mLocationCountryCode, mLocationCountryCodeUpdatedTimestampNanos);
+        }
+    }
+
+    /**
+     * This API should be used only when {@link #getCurrentNetworkCountryIso()} returns an empty
+     * list.
+     *
+     * @return The list of cached network country codes and their updated timestamps.
+     */
+    @NonNull public Map<String, Long> getCachedNetworkCountryIsoInfo() {
+        synchronized (mLock) {
+            if (mIsCountryCodesOverridden) {
+                logd("mOverriddenCachedNetworkCountryCodes = "
+                        + String.join(", ", mOverriddenCachedNetworkCountryCodes.keySet())
+                        + " will be used");
+                return mOverriddenCachedNetworkCountryCodes;
+            }
+            Map<String, Long> result = new HashMap<>();
+            for (NetworkCountryCodeInfo countryCodeInfo :
+                    mNetworkCountryCodeInfoPerPhone.values()) {
+                boolean alreadyAdded = result.containsKey(countryCodeInfo.countryCode);
+                if (!alreadyAdded || (alreadyAdded
+                        && result.get(countryCodeInfo.countryCode) < countryCodeInfo.timestamp)) {
+                    result.put(countryCodeInfo.countryCode, countryCodeInfo.timestamp);
+                }
+            }
+            return result;
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case EVENT_LOCATION_CHANGED:
+                queryCountryCodeForLocation((Location) msg.obj);
+                break;
+            case EVENT_LOCATION_COUNTRY_CODE_CHANGED:
+                setLocationCountryCode((Pair) msg.obj);
+                break;
+            case EVENT_NETWORK_COUNTRY_CODE_CHANGED:
+                handleNetworkCountryCodeChangedEvent((NetworkCountryCodeInfo) msg.obj);
+                break;
+            case EVENT_WIFI_CONNECTIVITY_STATE_CHANGED:
+            case EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET:
+                evaluateRequestingLocationUpdates();
+                break;
+            default:
+                logw("CountryDetectorHandler: unexpected message code: " + msg.what);
+                break;
+        }
+    }
+
+    /**
+     * This API is called by {@link LocaleTracker} whenever there is a change in network country
+     * code of a phone.
+     */
+    public void onNetworkCountryCodeChanged(
+            @NonNull Phone phone, @Nullable String currentCountryCode) {
+        NetworkCountryCodeInfo networkCountryCodeInfo = new NetworkCountryCodeInfo();
+        networkCountryCodeInfo.phoneId = phone.getPhoneId();
+        networkCountryCodeInfo.countryCode = currentCountryCode;
+        sendRequestAsync(EVENT_NETWORK_COUNTRY_CODE_CHANGED, networkCountryCodeInfo);
+    }
+
+    /**
+     * This API should be used by only CTS tests to forcefully set the telephony country codes.
+     */
+    public boolean setCountryCodes(boolean reset, @NonNull List<String> currentNetworkCountryCodes,
+            @NonNull Map<String, Long> cachedNetworkCountryCodes, String locationCountryCode,
+            long locationCountryCodeTimestampNanos) {
+        if (!isMockModemAllowed()) {
+            logd("setCountryCodes: mock modem is not allowed");
+            return false;
+        }
+        logd("setCountryCodes: currentNetworkCountryCodes="
+                + String.join(", ", currentNetworkCountryCodes)
+                + ", locationCountryCode=" + locationCountryCode
+                + ", locationCountryCodeTimestampNanos" + locationCountryCodeTimestampNanos
+                + ", reset=" + reset + ", cachedNetworkCountryCodes="
+                + String.join(", ", cachedNetworkCountryCodes.keySet()));
+
+        synchronized (mLock) {
+            if (reset) {
+                mIsCountryCodesOverridden = false;
+            } else {
+                mIsCountryCodesOverridden = true;
+                mOverriddenCachedNetworkCountryCodes = cachedNetworkCountryCodes;
+                mOverriddenCurrentNetworkCountryCodes = currentNetworkCountryCodes;
+                mOverriddenLocationCountryCode = locationCountryCode;
+                mOverriddenLocationCountryCodeUpdatedTimestampNanos =
+                        locationCountryCodeTimestampNanos;
+            }
+        }
+        return true;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void queryCountryCodeForLocation(@NonNull Location location) {
+        mGeocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1,
+                new TelephonyGeocodeListener(location.getElapsedRealtimeNanos()));
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected long getElapsedRealtimeNanos() {
+        return SystemClock.elapsedRealtimeNanos();
+    }
+
+    private void initialize() {
+        evaluateRequestingLocationUpdates();
+        registerForWifiConnectivityStateChanged();
+    }
+
+    private boolean isGeoCoderImplemented() {
+        return Geocoder.isPresent();
+    }
+
+    private void registerForLocationUpdates() {
+        // If the device does not implement Geocoder, there is no point trying to get location
+        // updates because we cannot retrieve the country based on the location anyway.
+        if (!isGeoCoderImplemented()) {
+            logd("Geocoder is not implemented on the device");
+            return;
+        }
+
+        synchronized (mLock) {
+            if (mIsLocationUpdateRequested) {
+                logd("Already registered for location updates");
+                return;
+            }
+
+            logd("Registering for location updates");
+            /*
+             * PASSIVE_PROVIDER can be used to passively receive location updates when other
+             * applications or services request them without actually requesting the locations
+             * ourselves. This provider will only return locations generated by other providers.
+             * This provider is used to make sure there is no impact on the thermal and battery of
+             * a device.
+             */
+            mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
+                    TIME_BETWEEN_LOCATION_UPDATES_MILLIS, DISTANCE_BETWEEN_LOCATION_UPDATES_METERS,
+                    mLocationListener);
+            mIsLocationUpdateRequested = true;
+            mLocationListener.onLocationChanged(getLastKnownLocation());
+        }
+    }
+
+    @Nullable
+    private Location getLastKnownLocation() {
+        Location result = null;
+        for (String provider : mLocationManager.getProviders(true)) {
+            Location location = mLocationManager.getLastKnownLocation(provider);
+            if (location != null && (result == null
+                    || result.getElapsedRealtimeNanos() < location.getElapsedRealtimeNanos())) {
+                result = location;
+            }
+        }
+        return result;
+    }
+
+    private void unregisterForLocationUpdates() {
+        synchronized (mLock) {
+            if (!mIsLocationUpdateRequested) {
+                logd("Location update was not requested yet");
+                return;
+            }
+            if (isLocationUpdateRequestQuotaExceeded()) {
+                logd("Removing location updates will be re-evaluated after the quota is refilled");
+                return;
+            }
+            mLocationManager.removeUpdates(mLocationListener);
+            mIsLocationUpdateRequested = false;
+            sendMessageDelayed(obtainMessage(EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET),
+                    WAIT_FOR_LOCATION_UPDATE_REQUEST_QUOTA_RESET_TIMEOUT_MILLIS);
+        }
+    }
+
+    private boolean isLocationUpdateRequestQuotaExceeded() {
+        return hasMessages(EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET);
+    }
+
+    private boolean shouldRequestLocationUpdate() {
+        return getCurrentNetworkCountryIso().isEmpty() && isWifiNetworkConnected();
+    }
+
+    /**
+     * Posts the specified command to be executed on the main thread and returns immediately.
+     *
+     * @param command command to be executed on the main thread
+     * @param argument additional parameters required to perform of the operation
+     */
+    private void sendRequestAsync(int command, @NonNull Object argument) {
+        Message msg = this.obtainMessage(command, argument);
+        msg.sendToTarget();
+    }
+
+    private void handleNetworkCountryCodeChangedEvent(
+            @NonNull NetworkCountryCodeInfo currentNetworkCountryCodeInfo) {
+        logd("currentNetworkCountryCodeInfo=" + currentNetworkCountryCodeInfo);
+        if (isValid(currentNetworkCountryCodeInfo.countryCode)) {
+            synchronized (mLock) {
+                NetworkCountryCodeInfo cachedNetworkCountryCodeInfo =
+                        mNetworkCountryCodeInfoPerPhone.computeIfAbsent(
+                                currentNetworkCountryCodeInfo.phoneId,
+                                k -> new NetworkCountryCodeInfo());
+                cachedNetworkCountryCodeInfo.phoneId = currentNetworkCountryCodeInfo.phoneId;
+                cachedNetworkCountryCodeInfo.timestamp = getElapsedRealtimeNanos();
+                cachedNetworkCountryCodeInfo.countryCode =
+                        currentNetworkCountryCodeInfo.countryCode.toUpperCase(Locale.US);
+            }
+        } else {
+            logd("handleNetworkCountryCodeChangedEvent: Got invalid or empty country code for "
+                    + "phoneId=" + currentNetworkCountryCodeInfo.phoneId);
+            synchronized (mLock) {
+                if (mNetworkCountryCodeInfoPerPhone.containsKey(
+                        currentNetworkCountryCodeInfo.phoneId)) {
+                    // The country code has changed from valid to invalid. Thus, we need to update
+                    // the last valid timestamp.
+                    NetworkCountryCodeInfo cachedNetworkCountryCodeInfo =
+                            mNetworkCountryCodeInfoPerPhone.get(
+                                    currentNetworkCountryCodeInfo.phoneId);
+                    cachedNetworkCountryCodeInfo.timestamp = getElapsedRealtimeNanos();
+                }
+            }
+        }
+        evaluateRequestingLocationUpdates();
+    }
+
+    private void setLocationCountryCode(@NonNull Pair<String, Long> countryCodeInfo) {
+        logd("Set location country code to: " + countryCodeInfo.first);
+        if (!isValid(countryCodeInfo.first)) {
+            logd("Received invalid location country code");
+        } else {
+            synchronized (mLock) {
+                mLocationCountryCode = countryCodeInfo.first.toUpperCase(Locale.US);
+                mLocationCountryCodeUpdatedTimestampNanos = countryCodeInfo.second;
+            }
+        }
+    }
+
+    private String getNetworkCountryIsoForPhone(@NonNull Phone phone) {
+        ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker();
+        if (serviceStateTracker == null) {
+            logw("getNetworkCountryIsoForPhone: serviceStateTracker is null");
+            return null;
+        }
+
+        LocaleTracker localeTracker = serviceStateTracker.getLocaleTracker();
+        if (localeTracker == null) {
+            logw("getNetworkCountryIsoForPhone: localeTracker is null");
+            return null;
+        }
+
+        return localeTracker.getCurrentCountry();
+    }
+
+    private void registerForWifiConnectivityStateChanged() {
+        logd("registerForWifiConnectivityStateChanged");
+        NetworkRequest.Builder builder = new NetworkRequest.Builder();
+        builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        ConnectivityManager.NetworkCallback networkCallback =
+                new ConnectivityManager.NetworkCallback() {
+                    @Override
+                    public void onAvailable(Network network) {
+                        logd("Wifi network available: " + network);
+                        sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null);
+                    }
+
+                    @Override
+                    public void onLost(Network network) {
+                        logd("Wifi network lost: " + network);
+                        sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null);
+                    }
+
+                    @Override
+                    public void onUnavailable() {
+                        logd("Wifi network unavailable");
+                        sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null);
+                    }
+                };
+        mConnectivityManager.registerNetworkCallback(builder.build(), networkCallback);
+    }
+
+    private void evaluateRequestingLocationUpdates() {
+        if (shouldRequestLocationUpdate()) {
+            registerForLocationUpdates();
+        } else {
+            unregisterForLocationUpdates();
+        }
+    }
+
+    private boolean isWifiNetworkConnected() {
+        Network activeNetwork = mConnectivityManager.getActiveNetwork();
+        NetworkCapabilities networkCapabilities =
+                mConnectivityManager.getNetworkCapabilities(activeNetwork);
+        return networkCapabilities != null
+                && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+                && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+    }
+
+    /**
+     * Check whether this is a valid country code.
+     *
+     * @param countryCode A 2-Character alphanumeric country code.
+     * @return {@code true} if the countryCode is valid, {@code false} otherwise.
+     */
+    private static boolean isValid(String countryCode) {
+        return countryCode != null && countryCode.length() == 2
+                && countryCode.chars().allMatch(Character::isLetterOrDigit);
+    }
+
+    private static boolean isMockModemAllowed() {
+        return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)
+                || SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false));
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void logw(@NonNull String log) {
+        Rlog.w(TAG, log);
+    }
+
+    private static void loge(@NonNull String log) {
+        Rlog.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/WapPushCache.java b/src/java/com/android/internal/telephony/WapPushCache.java
new file mode 100644
index 0000000..70d12e2
--- /dev/null
+++ b/src/java/com/android/internal/telephony/WapPushCache.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 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;
+
+import android.annotation.NonNull;
+import android.telephony.Rlog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Caches WAP push PDU data for retrieval during MMS downloading.
+ * When on a satellite connection, the cached message size will be used to prevent downloading
+ * messages that exceed a threshold.
+ *
+ * The cache uses a circular buffer and will start invalidating the oldest entries after 250
+ * message sizes have been inserted.
+ * The cache also invalidates entries that have been in the cache for over 14 days.
+ */
+public class WapPushCache {
+    private static final String TAG = "WAP PUSH CACHE";
+
+    // Because we store each size twice, this represents 250 messages. That limit is chosen so
+    // that the memory footprint of the cache stays reasonably small while still supporting what
+    // we guess will be the vast majority of real use cases.
+    private static final int MAX_CACHE_SIZE = 500;
+
+    // WAP push PDUs have an expiry property, but we can't be certain that it is set accurately
+    // by the carrier. We will use our own expiry for this cache to keep it small. One example
+    // carrier has an expiry of 7 days so 14 will give us room for those with longer times as well.
+    private static final long CACHE_EXPIRY_TIME = TimeUnit.DAYS.toMillis(14);
+
+    private static final HashMap<String, CacheEntry> sMessageSizes = new LinkedHashMap<>() {
+        @Override
+        protected boolean removeEldestEntry(Entry<String, CacheEntry> eldest) {
+            return size() > MAX_CACHE_SIZE;
+        }
+    };
+
+    @VisibleForTesting
+    public static TelephonyFacade sTelephonyFacade = new TelephonyFacade();
+
+    /**
+     * Puts a WAP push PDU's messageSize in the cache.
+     *
+     * The data is stored twice, once using just locationUrl as the key and once
+     * using transactionId appended to the locationUrl. For some carriers, xMS apps
+     * append the transactionId to the location and we need to support lookup using either the
+     * original location or one modified in this way.
+
+     *
+     * @param locationUrl location of the message used as part of the cache key.
+     * @param transactionId message transaction ID used as part of the cache key.
+     * @param messageSize size of the message to be stored in the cache.
+     */
+    public static void putWapMessageSize(
+            @NonNull byte[] locationUrl,
+            @NonNull byte[] transactionId,
+            long messageSize
+    ) {
+        long expiry = sTelephonyFacade.getElapsedSinceBootMillis() + CACHE_EXPIRY_TIME;
+        if (messageSize <= 0) {
+            Rlog.e(TAG, "Invalid message size of " + messageSize + ". Not inserting.");
+            return;
+        }
+        synchronized (sMessageSizes) {
+            sMessageSizes.put(Arrays.toString(locationUrl), new CacheEntry(messageSize, expiry));
+
+            // concatenate the locationUrl and transactionId
+            byte[] joinedKey = ByteBuffer
+                    .allocate(locationUrl.length + transactionId.length)
+                    .put(locationUrl)
+                    .put(transactionId)
+                    .array();
+            sMessageSizes.put(Arrays.toString(joinedKey), new CacheEntry(messageSize, expiry));
+            invalidateOldEntries();
+        }
+    }
+
+    /**
+     * Remove entries from the cache that are older than CACHE_EXPIRY_TIME
+     */
+    private static void invalidateOldEntries() {
+        long currentTime = sTelephonyFacade.getElapsedSinceBootMillis();
+
+        // We can just remove elements from the start until one is found that does not exceed the
+        // expiry since the elements are in order of insertion.
+        for (Iterator<CacheEntry> it = sMessageSizes.values().iterator(); it.hasNext(); ) {
+            CacheEntry entry = it.next();
+            if (entry.mExpiry < currentTime) {
+                it.remove();
+            } else {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Gets the message size of a WAP from the cache.
+     *
+     * Because we stored the size both using the location+transactionId key and using the
+     * location only key, we should be able to find the size whether the xMS app modified
+     * the location or not.
+     *
+     * @param locationUrl the location to use as a key for looking up the size in the cache.
+     *
+     * @return long representing the message size of the WAP
+     * @throws NoSuchElementException if the WAP doesn't exist in the cache
+     * @throws IllegalArgumentException if the locationUrl is empty
+     */
+    public static long getWapMessageSize(@NonNull byte[] locationUrl) {
+        if (locationUrl.length == 0) {
+            throw new IllegalArgumentException("Found empty locationUrl");
+        }
+        CacheEntry entry = sMessageSizes.get(Arrays.toString(locationUrl));
+        if (entry == null) {
+            throw new NoSuchElementException(
+                "No cached WAP size for locationUrl " + Arrays.toString(locationUrl)
+            );
+        }
+        return entry.mSize;
+    }
+
+    /**
+     * Clears all elements from the cache
+     */
+    @VisibleForTesting
+    public static void clear() {
+        sMessageSizes.clear();
+    }
+
+    /**
+     * Returns a count of the number of elements in the cache
+     * @return count of elements
+     */
+    @VisibleForTesting
+    public static int size() {
+        return sMessageSizes.size();
+    }
+
+
+
+    private static class CacheEntry {
+        CacheEntry(long size, long expiry) {
+            mSize = size;
+            mExpiry = expiry;
+        }
+        private final long mSize;
+        private final long mExpiry;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
index d6ea4ad..7669411 100644
--- a/src/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -262,6 +262,13 @@
 
             if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) {
                 final NotificationInd nInd = (NotificationInd) parsedPdu;
+                // save the WAP push message size so that if a download request is made for it
+                // while on a satellite connection we can check if the size is under the threshold
+                WapPushCache.putWapMessageSize(
+                        nInd.getContentLocation(),
+                        nInd.getTransactionId(),
+                        nInd.getMessageSize()
+                );
                 if (nInd.getFrom() != null
                         && BlockChecker.isBlocked(mContext, nInd.getFrom().getString(), null)) {
                     result.statusCode = Intents.RESULT_SMS_HANDLED;
diff --git a/src/java/com/android/internal/telephony/analytics/CallAnalyticsProvider.java b/src/java/com/android/internal/telephony/analytics/CallAnalyticsProvider.java
new file mode 100644
index 0000000..e0da0f1
--- /dev/null
+++ b/src/java/com/android/internal/telephony/analytics/CallAnalyticsProvider.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import static android.os.Build.VERSION.INCREMENTAL;
+
+import static com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.DATE_FORMAT;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.CallAnalyticsTable;
+import com.android.telephony.Rlog;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+
+/**
+ * Provider class for calls, receives the data from CallAnalytics and performs business logic on the
+ * received data. It uses util class for required db operation. This class implements the
+ * TelephonyAnalyticsProvider interface to provide aggregation functionality.
+ */
+public class CallAnalyticsProvider implements TelephonyAnalyticsProvider {
+    private static final String TAG = CallAnalyticsProvider.class.getSimpleName();
+    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.00");
+    private TelephonyAnalyticsUtil mTelephonyAnalyticsUtil;
+    private String mDateOfDeletedRecordsCallTable;
+    private static final String CREATE_CALL_ANALYTICS_TABLE =
+            "CREATE TABLE IF NOT EXISTS "
+                    + CallAnalyticsTable.TABLE_NAME
+                    + "("
+                    + CallAnalyticsTable._ID
+                    + " INTEGER PRIMARY KEY,"
+                    + CallAnalyticsTable.LOG_DATE
+                    + " DATE ,"
+                    + CallAnalyticsTable.CALL_STATUS
+                    + " TEXT DEFAULT '',"
+                    + CallAnalyticsTable.CALL_TYPE
+                    + " TEXT DEFAULT '',"
+                    + CallAnalyticsTable.RAT
+                    + " TEXT DEFAULT '',"
+                    + CallAnalyticsTable.SLOT_ID
+                    + " INTEGER ,"
+                    + CallAnalyticsTable.FAILURE_REASON
+                    + " TEXT DEFAULT '',"
+                    + CallAnalyticsTable.RELEASE_VERSION
+                    + " TEXT DEFAULT '' , "
+                    + CallAnalyticsTable.COUNT
+                    + " INTEGER DEFAULT 1 "
+                    + ");";
+
+    private static final String[] CALL_INSERTION_PROJECTION = {
+        CallAnalyticsTable._ID, CallAnalyticsTable.COUNT
+    };
+
+    private static final String CALL_SUCCESS_INSERTION_SELECTION =
+            CallAnalyticsTable.CALL_TYPE
+                    + " = ? AND "
+                    + CallAnalyticsTable.LOG_DATE
+                    + " = ? AND "
+                    + CallAnalyticsTable.CALL_STATUS
+                    + " = ? AND "
+                    + CallAnalyticsTable.SLOT_ID
+                    + " = ? ";
+
+    private static final String CALL_FAILED_INSERTION_SELECTION =
+            CallAnalyticsTable.LOG_DATE
+                    + " = ? AND "
+                    + CallAnalyticsTable.CALL_STATUS
+                    + " = ? AND "
+                    + CallAnalyticsTable.CALL_TYPE
+                    + " = ? AND "
+                    + CallAnalyticsTable.SLOT_ID
+                    + " = ? AND "
+                    + CallAnalyticsTable.RAT
+                    + " = ? AND "
+                    + CallAnalyticsTable.FAILURE_REASON
+                    + " = ? AND "
+                    + CallAnalyticsTable.RELEASE_VERSION
+                    + " = ? ";
+
+    private static final String CALL_OLD_DATA_DELETION_SELECTION =
+            CallAnalyticsTable.LOG_DATE + " < ? ";
+
+    private static final String CALL_OVERFLOW_DATA_DELETION_SELECTION =
+            CallAnalyticsTable._ID
+                    + " IN "
+                    + " ( SELECT "
+                    + CallAnalyticsTable._ID
+                    + " FROM "
+                    + CallAnalyticsTable.TABLE_NAME
+                    + " ORDER BY "
+                    + CallAnalyticsTable.LOG_DATE
+                    + " DESC LIMIT -1 OFFSET ? )";
+
+    private enum CallStatus {
+        SUCCESS("Success"),
+        FAILURE("Failure");
+        public String value;
+
+        CallStatus(String value) {
+            this.value = value;
+        }
+    }
+
+    private enum CallType {
+        NORMAL("Normal Call"),
+        SOS("SOS Call");
+        public String value;
+
+        CallType(String value) {
+            this.value = value;
+        }
+    }
+
+    private final int mSlotIndex;
+
+    /**
+     * Initializes the CallAnalyticsProvider object and creates a table in the DB to log the
+     * information related to Calls.
+     *
+     * @param telephonyAnalyticsUtil : Util Class object to support db operations
+     * @param slotIndex : Logical slot index.
+     */
+    public CallAnalyticsProvider(TelephonyAnalyticsUtil telephonyAnalyticsUtil, int slotIndex) {
+        mTelephonyAnalyticsUtil = telephonyAnalyticsUtil;
+        mSlotIndex = slotIndex;
+        mTelephonyAnalyticsUtil.createTable(CREATE_CALL_ANALYTICS_TABLE);
+    }
+
+    private ContentValues getContentValues(
+            String callType, String callStatus, int slotId, String rat, String failureReason) {
+        ContentValues values = new ContentValues();
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        values.put(CallAnalyticsTable.LOG_DATE, dateToday);
+        values.put(CallAnalyticsTable.CALL_TYPE, callType);
+        values.put(CallAnalyticsTable.CALL_STATUS, callStatus);
+        values.put(CallAnalyticsTable.SLOT_ID, slotId);
+        values.put(CallAnalyticsTable.RAT, rat);
+        values.put(CallAnalyticsTable.FAILURE_REASON, failureReason);
+        values.put(CallAnalyticsTable.RELEASE_VERSION, INCREMENTAL);
+        return values;
+    }
+
+    private String[] getSuccessfulCallSelectionArgs(ContentValues values) {
+        return new String[] {
+            values.getAsString(CallAnalyticsTable.CALL_TYPE),
+            values.getAsString(CallAnalyticsTable.LOG_DATE),
+            CallStatus.SUCCESS.value,
+            values.getAsString(CallAnalyticsTable.SLOT_ID)
+        };
+    }
+
+    private String[] getFailedCallSelectionArgs(ContentValues values) {
+
+        return new String[] {
+            values.getAsString(CallAnalyticsTable.LOG_DATE),
+            values.getAsString(CallAnalyticsTable.CALL_STATUS),
+            values.getAsString(CallAnalyticsTable.CALL_TYPE),
+            values.getAsString(CallAnalyticsTable.SLOT_ID),
+            values.getAsString(CallAnalyticsTable.RAT),
+            values.getAsString(CallAnalyticsTable.FAILURE_REASON),
+            values.getAsString(CallAnalyticsTable.RELEASE_VERSION)
+        };
+    }
+
+    /**
+     * Receives data, processes it and sends for insertion to db.
+     *
+     * @param callType : Type of the Call , i.e. Normal or Sos
+     * @param callStatus : Defines call was success or failure
+     * @param slotId : Logical Slot index derived from Phone object.
+     * @param rat : Radio Access Technology on which call ended.
+     * @param failureReason : Failure Reason of the call.
+     */
+    public void insertDataToDb(
+            String callType, String callStatus, int slotId, String rat, String failureReason) {
+        ContentValues values = getContentValues(callType, callStatus, slotId, rat, failureReason);
+        Cursor cursor = null;
+        try {
+            if (values.getAsString(CallAnalyticsTable.CALL_STATUS)
+                    .equals(CallStatus.SUCCESS.value)) {
+                Rlog.d(TAG, "Insertion for Success Call");
+                String[] selectionArgs = getSuccessfulCallSelectionArgs(values);
+                cursor =
+                        mTelephonyAnalyticsUtil.getCursor(
+                                CallAnalyticsTable.TABLE_NAME,
+                                CALL_INSERTION_PROJECTION,
+                                CALL_SUCCESS_INSERTION_SELECTION,
+                                selectionArgs,
+                                null,
+                                null,
+                                null,
+                                null);
+            } else {
+
+                String[] selectionArgs = getFailedCallSelectionArgs(values);
+                cursor =
+                        mTelephonyAnalyticsUtil.getCursor(
+                                CallAnalyticsTable.TABLE_NAME,
+                                CALL_INSERTION_PROJECTION,
+                                CALL_FAILED_INSERTION_SELECTION,
+                                selectionArgs,
+                                null,
+                                null,
+                                null,
+                                null);
+            }
+            updateEntryIfExistsOrInsert(cursor, values);
+            deleteOldAndOverflowData();
+        } catch (Exception e) {
+            Rlog.e(TAG, "Error caught in insertDataToDb while insertion.");
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    private void updateEntryIfExistsOrInsert(Cursor cursor, ContentValues values) {
+        if (cursor != null && cursor.moveToFirst()) {
+            int idColumnIndex = cursor.getColumnIndex(CallAnalyticsTable._ID);
+            int countColumnIndex = cursor.getColumnIndex(CallAnalyticsTable.COUNT);
+            if (idColumnIndex != -1 && countColumnIndex != -1) {
+                int id = cursor.getInt(idColumnIndex);
+                int count = cursor.getInt(countColumnIndex);
+                int newCount = count + 1;
+
+                values.put(CallAnalyticsTable.COUNT, newCount);
+
+                String updateSelection = CallAnalyticsTable._ID + " = ? ";
+                String[] updateSelectionArgs = {String.valueOf(id)};
+                Rlog.d(TAG, "Updated Count = " + values.getAsString(CallAnalyticsTable.COUNT));
+
+                mTelephonyAnalyticsUtil.update(
+                        CallAnalyticsTable.TABLE_NAME,
+                        values,
+                        updateSelection,
+                        updateSelectionArgs);
+            }
+        } else {
+            Rlog.d(TAG, "Simple Insertion");
+            mTelephonyAnalyticsUtil.insert(CallAnalyticsTable.TABLE_NAME, values);
+        }
+    }
+
+    /** Gets the count stored in the cursor object. */
+    @VisibleForTesting
+    public long getCount(
+            String tableName,
+            String[] columns,
+            String selection,
+            String[] selectionArgs,
+            String groupBy,
+            String having,
+            String orderBy,
+            String limit) {
+        Cursor cursor = null;
+        long totalCount = 0;
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            CallAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            groupBy,
+                            having,
+                            orderBy,
+                            limit);
+            totalCount = mTelephonyAnalyticsUtil.getCountFromCursor(cursor);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return totalCount;
+    }
+
+    private String[] getColumnSelectionAndArgs(String callType, String callStatus) {
+        String selection;
+        String[] selectionAndArgs;
+        if (callType != null) {
+            if (callStatus != null) {
+                selection =
+                        CallAnalyticsTable.CALL_TYPE
+                                + " = ? AND "
+                                + CallAnalyticsTable.CALL_STATUS
+                                + " = ? AND "
+                                + CallAnalyticsTable.SLOT_ID
+                                + " = ? ";
+                selectionAndArgs =
+                        new String[] {
+                            selection, callType, callStatus, Integer.toString(mSlotIndex)
+                        };
+            } else {
+                selection =
+                        CallAnalyticsTable.CALL_TYPE
+                                + " = ? AND "
+                                + CallAnalyticsTable.SLOT_ID
+                                + " = ? ";
+                selectionAndArgs = new String[] {selection, callType, Integer.toString(mSlotIndex)};
+            }
+        } else {
+            if (callStatus != null) {
+                selection =
+                        CallAnalyticsTable.CALL_STATUS
+                                + " = ? AND "
+                                + CallAnalyticsTable.SLOT_ID
+                                + " = ? ";
+                selectionAndArgs =
+                        new String[] {selection, callStatus, Integer.toString(mSlotIndex)};
+            } else {
+                selection = CallAnalyticsTable.SLOT_ID + " = ? ";
+                selectionAndArgs = new String[] {selection, Integer.toString(mSlotIndex)};
+            }
+        }
+        return selectionAndArgs;
+    }
+
+    private long countCallsOfTypeAndStatus(String callType, String callStatus) {
+        int selectionIndex = 0;
+        String[] columns = {"sum(" + CallAnalyticsTable.COUNT + ")"};
+        String[] selectionAndArgs = getColumnSelectionAndArgs(callType, callStatus);
+        String selection = selectionAndArgs[selectionIndex];
+        int selectionArgsStartIndex = 1, selectionArgsEndIndex = selectionAndArgs.length;
+        String[] selectionArgs =
+                Arrays.copyOfRange(
+                        selectionAndArgs, selectionArgsStartIndex, selectionArgsEndIndex);
+        return getCount(
+                CallAnalyticsTable.TABLE_NAME,
+                columns,
+                selection,
+                selectionArgs,
+                null,
+                null,
+                null,
+                null);
+    }
+
+    private long countTotalCalls() {
+        return countCallsOfTypeAndStatus(null, null);
+    }
+
+    private long countFailedCalls() {
+        return countCallsOfTypeAndStatus(null, CallStatus.FAILURE.value);
+    }
+
+    private long countNormalCalls() {
+        return countCallsOfTypeAndStatus(CallType.NORMAL.value, null);
+    }
+
+    private long countFailedNormalCalls() {
+        return countCallsOfTypeAndStatus(CallType.NORMAL.value, CallStatus.FAILURE.value);
+    }
+
+    private long countSosCalls() {
+        return countCallsOfTypeAndStatus(CallType.SOS.value, null);
+    }
+
+    private long countFailedSosCalls() {
+        return countCallsOfTypeAndStatus(CallType.SOS.value, CallStatus.FAILURE.value);
+    }
+
+    private String getMaxFailureVersion() {
+        String[] columns = {CallAnalyticsTable.RELEASE_VERSION};
+        String selection =
+                CallAnalyticsTable.CALL_STATUS + " = ? AND " + CallAnalyticsTable.SLOT_ID + " = ? ";
+        String[] selectionArgs = {CallStatus.FAILURE.value, Integer.toString(mSlotIndex)};
+        String groupBy = CallAnalyticsTable.RELEASE_VERSION;
+        String orderBy = "SUM(" + CallAnalyticsTable.COUNT + ") DESC ";
+        String limit = "1";
+        Cursor cursor = null;
+        String version = "";
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            CallAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            groupBy,
+                            null,
+                            orderBy,
+                            limit);
+
+            if (cursor != null && cursor.moveToFirst()) {
+                int releaseVersionColumnIndex =
+                        cursor.getColumnIndex(CallAnalyticsTable.RELEASE_VERSION);
+                if (releaseVersionColumnIndex != -1) {
+                    version = cursor.getString(releaseVersionColumnIndex);
+                }
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return version;
+    }
+
+    private String getMaxFailureNetworkType() {
+        String[] columns = {CallAnalyticsTable.RAT};
+        String selection =
+                CallAnalyticsTable.CALL_STATUS + " = ? AND " + CallAnalyticsTable.SLOT_ID + " = ? ";
+        String[] selectionArgs = {CallStatus.FAILURE.value, Integer.toString(mSlotIndex)};
+        String groupBy = CallAnalyticsTable.RAT;
+        String orderBy = "SUM(" + CallAnalyticsTable.COUNT + ") DESC ";
+        String limit = "1";
+        Cursor cursor = null;
+        String networkType = "";
+
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            CallAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            groupBy,
+                            null,
+                            orderBy,
+                            limit);
+
+            if (cursor != null && cursor.moveToFirst()) {
+                int networkColumnIndex = cursor.getColumnIndex(CallAnalyticsTable.RAT);
+                networkType = cursor.getString(networkColumnIndex);
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        return networkType;
+    }
+
+    private HashMap<String, Integer> getFailureCountByRatForCallType(String callType) {
+        return getFailureCountByColumnForCallType(CallAnalyticsTable.RAT, callType);
+    }
+
+    private HashMap<String, Integer> getFailureCountByReasonForCallType(String callType) {
+        return getFailureCountByColumnForCallType(CallAnalyticsTable.FAILURE_REASON, callType);
+    }
+
+    private HashMap<String, Integer> getFailureCountByColumnForCallType(
+            String column, String callType) {
+        String[] columns = {column, "SUM(" + CallAnalyticsTable.COUNT + ") AS count"};
+        String selection =
+                CallAnalyticsTable.CALL_TYPE
+                        + " = ? AND "
+                        + CallAnalyticsTable.CALL_STATUS
+                        + " = ? AND "
+                        + CallAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {callType, CallStatus.FAILURE.value, Integer.toString(mSlotIndex)};
+        String groupBy = column;
+        Cursor cursor = null;
+        HashMap<String, Integer> failureCountByReason = new HashMap<>();
+
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            CallAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            groupBy,
+                            null,
+                            null,
+                            null);
+
+            if (cursor != null) {
+                int failureColumnIndex = cursor.getColumnIndex(column);
+                int failureCountColumnIndex = cursor.getColumnIndex("count");
+
+                if (failureCountColumnIndex != -1 && failureColumnIndex != -1) {
+                    while (cursor.moveToNext()) {
+                        String failureReason = cursor.getString(failureColumnIndex);
+                        int failureCount = cursor.getInt(failureCountColumnIndex);
+                        failureCountByReason.put(failureReason, failureCount);
+                    }
+                }
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        return failureCountByReason;
+    }
+
+    protected void deleteOldAndOverflowData() {
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        if (mDateOfDeletedRecordsCallTable == null
+                || !mDateOfDeletedRecordsCallTable.equals(dateToday)) {
+            mTelephonyAnalyticsUtil.deleteOverflowAndOldData(
+                    CallAnalyticsTable.TABLE_NAME,
+                    CALL_OVERFLOW_DATA_DELETION_SELECTION,
+                    CALL_OLD_DATA_DELETION_SELECTION);
+            mDateOfDeletedRecordsCallTable = dateToday;
+        }
+    }
+
+    /** Setter function for mDateOfDeletedRecordsCallTable. */
+    public void setDateOfDeletedRecordsCallTable(String dateOfDeletedRecordsCallTable) {
+        mDateOfDeletedRecordsCallTable = dateOfDeletedRecordsCallTable;
+    }
+
+    private ArrayList<String> dumpInformationInList(
+            long totalCalls,
+            long failedCalls,
+            double percentageFailedCalls,
+            long normalCallsCount,
+            double percentageFailedNormalCalls,
+            long sosCallCount,
+            double percentageFailedSosCalls,
+            String maxFailureVersion,
+            String maxFailureNetworkType,
+            HashMap<String, Integer> failureCountByReasonNormalCall,
+            HashMap<String, Integer> failureCountByReasonSosCall,
+            HashMap<String, Integer> failureCountByRatNormalCall,
+            HashMap<String, Integer> failureCountByRatSosCall) {
+
+        ArrayList<String> aggregatedCallInformation = new ArrayList<>();
+        aggregatedCallInformation.add("Normal Call Stats");
+        aggregatedCallInformation.add("\tTotal Normal Calls = " + normalCallsCount);
+        aggregatedCallInformation.add(
+                "\tPercentage Failure of Normal Calls = "
+                        + DECIMAL_FORMAT.format(percentageFailedNormalCalls)
+                        + "%");
+        addFailureStatsFromHashMap(
+                failureCountByReasonNormalCall,
+                CallType.NORMAL.value,
+                normalCallsCount,
+                CallAnalyticsTable.FAILURE_REASON,
+                aggregatedCallInformation);
+        addFailureStatsFromHashMap(
+                failureCountByRatNormalCall,
+                CallType.NORMAL.value,
+                normalCallsCount,
+                CallAnalyticsTable.RAT,
+                aggregatedCallInformation);
+
+        if (sosCallCount > 0) {
+            aggregatedCallInformation.add("SOS Call Stats");
+            aggregatedCallInformation.add("\tTotal SOS Calls = " + sosCallCount);
+            aggregatedCallInformation.add(
+                    "\tPercentage Failure of SOS Calls = "
+                            + DECIMAL_FORMAT.format(percentageFailedSosCalls)
+                            + "%");
+            addFailureStatsFromHashMap(
+                    failureCountByReasonSosCall,
+                    CallType.SOS.value,
+                    sosCallCount,
+                    CallAnalyticsTable.FAILURE_REASON,
+                    aggregatedCallInformation);
+            addFailureStatsFromHashMap(
+                    failureCountByRatSosCall,
+                    CallType.SOS.value,
+                    sosCallCount,
+                    CallAnalyticsTable.RAT,
+                    aggregatedCallInformation);
+        }
+        if (failedCalls != 0) {
+            aggregatedCallInformation.add(
+                    "\tMax Call(Normal+SOS) Failures at Version : " + maxFailureVersion);
+            aggregatedCallInformation.add(
+                    "\tMax Call(Normal+SOS) Failures at Network Type: " + maxFailureNetworkType);
+        }
+
+        return aggregatedCallInformation;
+    }
+
+    private void addFailureStatsFromHashMap(
+            HashMap<String, Integer> failureCountByColumn,
+            String callType,
+            long totalCallsOfCallType,
+            String column,
+            ArrayList<String> aggregatedCallInformation) {
+        failureCountByColumn.forEach(
+                (k, v) -> {
+                    double percentageFail = (double) v / (double) totalCallsOfCallType * 100.0;
+                    aggregatedCallInformation.add(
+                            "\tNo. of "
+                                    + callType
+                                    + " failures at "
+                                    + column
+                                    + " : "
+                                    + k
+                                    + " = "
+                                    + v
+                                    + ", Percentage = "
+                                    + DECIMAL_FORMAT.format(percentageFail)
+                                    + "%");
+                });
+    }
+
+    /**
+     * Collects all information which is intended to be a part of the report by calling the required
+     * functions implemented in the class.
+     *
+     * @return List which contains all the Calls related information
+     */
+    public ArrayList<String> aggregate() {
+        long totalCalls = countTotalCalls();
+        long failedCalls = countFailedCalls();
+        double percentageFailedCalls = (double) failedCalls / (double) totalCalls * 100.0;
+        String maxFailuresVersion = getMaxFailureVersion();
+        String maxFailuresNetworkType = getMaxFailureNetworkType();
+        HashMap<String, Integer> failureCountByReasonNormalCall =
+                getFailureCountByReasonForCallType(CallType.NORMAL.value);
+        HashMap<String, Integer> failureCountByReasonSosCall =
+                getFailureCountByReasonForCallType(CallType.SOS.value);
+        HashMap<String, Integer> failureCountByRatNormalCall =
+                getFailureCountByRatForCallType(CallType.NORMAL.value);
+        HashMap<String, Integer> failureCountByRatSosCall =
+                getFailureCountByRatForCallType(CallType.SOS.value);
+        long normalCallsCount = countNormalCalls();
+        long normalCallFailureCount = countFailedNormalCalls();
+        double percentageFailureNormalCall =
+                (double) normalCallFailureCount / (double) normalCallsCount * 100.0;
+        long sosCallCount = countSosCalls();
+        long sosCallFailureCount = countFailedSosCalls();
+        double percentageFailureSosCall =
+                (double) sosCallFailureCount / (double) sosCallCount * 100.0;
+        ArrayList<String> aggregatedCallInformation =
+                dumpInformationInList(
+                        totalCalls,
+                        failedCalls,
+                        percentageFailedCalls,
+                        normalCallsCount,
+                        percentageFailureNormalCall,
+                        sosCallCount,
+                        percentageFailureSosCall,
+                        maxFailuresVersion,
+                        maxFailuresNetworkType,
+                        failureCountByReasonNormalCall,
+                        failureCountByReasonSosCall,
+                        failureCountByRatNormalCall,
+                        failureCountByRatSosCall);
+        return aggregatedCallInformation;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/analytics/ServiceStateAnalyticsProvider.java b/src/java/com/android/internal/telephony/analytics/ServiceStateAnalyticsProvider.java
new file mode 100644
index 0000000..cae44bd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/analytics/ServiceStateAnalyticsProvider.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import static android.os.Build.VERSION.INCREMENTAL;
+
+import static com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.DATE_FORMAT;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.analytics.TelephonyAnalytics.ServiceStateAnalytics.TimeStampedServiceState;
+import com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.ServiceStateAnalyticsTable;
+import com.android.telephony.Rlog;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+
+/**
+ * Provider class for ServiceState. Receives the data from ServiceStateAnalytics. Performs business
+ * logic on the received data. Uses Util class for required db operation. Implements the
+ * TelephonyAnalyticsProvider interface to provide aggregation functionality.
+ */
+public class ServiceStateAnalyticsProvider implements TelephonyAnalyticsProvider {
+    protected TelephonyAnalyticsUtil mTelephonyAnalyticsUtil;
+    private static final String TAG = ServiceStateAnalyticsProvider.class.getSimpleName();
+    private static final String CREATE_SERVICE_STATE_TABLE_QUERY =
+            "CREATE TABLE IF NOT EXISTS "
+                    + ServiceStateAnalyticsTable.TABLE_NAME
+                    + " ( "
+                    + ServiceStateAnalyticsTable._ID
+                    + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+                    + ServiceStateAnalyticsTable.LOG_DATE
+                    + " DATE ,"
+                    + ServiceStateAnalyticsTable.SLOT_ID
+                    + " INTEGER , "
+                    + ServiceStateAnalyticsTable.TIME_DURATION
+                    + " INTEGER ,"
+                    + ServiceStateAnalyticsTable.RAT
+                    + " TEXT ,"
+                    + ServiceStateAnalyticsTable.DEVICE_STATUS
+                    + " TEXT ,"
+                    + ServiceStateAnalyticsTable.RELEASE_VERSION
+                    + " TEXT "
+                    + ");";
+
+    private static final String[] SERVICE_STATE_INSERTION_COLUMNS = {
+        ServiceStateAnalyticsTable._ID, ServiceStateAnalyticsTable.TIME_DURATION
+    };
+
+    private String mDateOfDeletedRecordsServiceStateTable;
+
+    private static final String SERVICE_STATE_INSERTION_SELECTION =
+            ServiceStateAnalyticsTable.LOG_DATE
+                    + " = ? AND "
+                    + ServiceStateAnalyticsTable.SLOT_ID
+                    + " = ? AND "
+                    + ServiceStateAnalyticsTable.RAT
+                    + " = ? AND "
+                    + ServiceStateAnalyticsTable.DEVICE_STATUS
+                    + " = ? AND "
+                    + ServiceStateAnalyticsTable.RELEASE_VERSION
+                    + " = ? ";
+
+    private static final String SERVICE_STATE_OVERFLOW_DATA_DELETION_SELECTION =
+            ServiceStateAnalyticsTable._ID
+                    + " IN "
+                    + " ( SELECT "
+                    + ServiceStateAnalyticsTable._ID
+                    + " FROM "
+                    + ServiceStateAnalyticsTable.TABLE_NAME
+                    + " ORDER BY "
+                    + ServiceStateAnalyticsTable.LOG_DATE
+                    + " DESC LIMIT -1 OFFSET ? )";
+
+    private static final String SERVICE_STATE_OLD_DATA_DELETION_SELECTION =
+            ServiceStateAnalyticsTable.LOG_DATE + " < ? ";
+
+    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.00");
+
+    private final int mSlotIndex;
+
+    /**
+     * Instantiates the ServiceStateAnalyticsProvider Object. Creates a table in the db for Storing
+     * ServiceState Related Information.
+     */
+    public ServiceStateAnalyticsProvider(TelephonyAnalyticsUtil databaseUtil, int slotIndex) {
+        mTelephonyAnalyticsUtil = databaseUtil;
+        mSlotIndex = slotIndex;
+        mTelephonyAnalyticsUtil.createTable(CREATE_SERVICE_STATE_TABLE_QUERY);
+    }
+
+    private ContentValues getContentValues(
+            TelephonyAnalytics.ServiceStateAnalytics.TimeStampedServiceState lastState,
+            long endTimeStamp) {
+        ContentValues values = new ContentValues();
+        long timeInterval = endTimeStamp - lastState.mTimestampStart;
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        values.put(ServiceStateAnalyticsTable.LOG_DATE, dateToday);
+        values.put(ServiceStateAnalyticsTable.TIME_DURATION, timeInterval);
+        values.put(ServiceStateAnalyticsTable.SLOT_ID, lastState.mSlotIndex);
+        values.put(ServiceStateAnalyticsTable.RAT, lastState.mRAT);
+        values.put(ServiceStateAnalyticsTable.DEVICE_STATUS, lastState.mDeviceStatus);
+        values.put(ServiceStateAnalyticsTable.RELEASE_VERSION, INCREMENTAL);
+
+        return values;
+    }
+
+    /** Receives the data, processes it and sends it for insertion to db. */
+    @VisibleForTesting
+    public void insertDataToDb(TimeStampedServiceState lastState, long endTimeStamp) {
+        ContentValues values = getContentValues(lastState, endTimeStamp);
+        Rlog.d(TAG, "  " + values.toString() + "Time = " + System.currentTimeMillis());
+        String[] selectionArgs = {
+            values.getAsString(ServiceStateAnalyticsTable.LOG_DATE),
+            values.getAsString(ServiceStateAnalyticsTable.SLOT_ID),
+            values.getAsString(ServiceStateAnalyticsTable.RAT),
+            values.getAsString(ServiceStateAnalyticsTable.DEVICE_STATUS),
+            values.getAsString(ServiceStateAnalyticsTable.RELEASE_VERSION)
+        };
+        Cursor cursor = null;
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            ServiceStateAnalyticsTable.TABLE_NAME,
+                            SERVICE_STATE_INSERTION_COLUMNS,
+                            SERVICE_STATE_INSERTION_SELECTION,
+                            selectionArgs,
+                            null,
+                            null,
+                            null,
+                            null);
+            updateIfEntryExistsOtherwiseInsert(cursor, values);
+            deleteOldAndOverflowData();
+
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    private void updateIfEntryExistsOtherwiseInsert(Cursor cursor, ContentValues values) {
+        if (cursor != null && cursor.moveToFirst()) {
+            int idIndex = cursor.getColumnIndex(ServiceStateAnalyticsTable._ID);
+            int timeDurationIndex = cursor.getColumnIndex(ServiceStateAnalyticsTable.TIME_DURATION);
+            if (idIndex != -1 && timeDurationIndex != -1) {
+                int id = cursor.getInt(idIndex);
+                int oldTimeDuration = cursor.getInt(timeDurationIndex);
+                int updatedTimeDuration =
+                        oldTimeDuration
+                                + Integer.parseInt(
+                                        values.getAsString(
+                                                ServiceStateAnalyticsTable.TIME_DURATION));
+                String updateSelection = ServiceStateAnalyticsTable._ID + " = ? ";
+                String[] updateSelectionArgs = {Integer.toString(id)};
+                values.put(ServiceStateAnalyticsTable.TIME_DURATION, updatedTimeDuration);
+                mTelephonyAnalyticsUtil.update(
+                        ServiceStateAnalyticsTable.TABLE_NAME,
+                        values,
+                        updateSelection,
+                        updateSelectionArgs);
+            }
+        } else {
+            mTelephonyAnalyticsUtil.insert(ServiceStateAnalyticsTable.TABLE_NAME, values);
+        }
+    }
+
+    private long getTotalUpTime() {
+        String[] columns = {"SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ")"};
+        String selection = ServiceStateAnalyticsTable.SLOT_ID + " = ? ";
+        String[] selectionArgs = {Integer.toString(mSlotIndex)};
+        Cursor cursor = null;
+        long duration = 0;
+
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            ServiceStateAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            null,
+                            null,
+                            null,
+                            null);
+            if (cursor != null && cursor.moveToFirst()) {
+                duration = cursor.getLong(0);
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        return duration;
+    }
+
+    private long outOfServiceDuration() {
+        String[] columns = {"SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ")"};
+        String selection =
+                ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " != ? "
+                        + " AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"IN_SERVICE", Integer.toString(mSlotIndex)};
+        long oosDuration = 0;
+        Cursor cursor = null;
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            ServiceStateAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            null,
+                            null,
+                            null,
+                            null);
+            if (cursor != null && cursor.moveToFirst()) {
+                oosDuration = cursor.getLong(0);
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return oosDuration;
+    }
+
+    private HashMap<String, Long> getOutOfServiceDurationByReason() {
+        HashMap<String, Long> outOfServiceDurationByReason = new HashMap<>();
+        String[] columns = {
+            ServiceStateAnalyticsTable.DEVICE_STATUS,
+            "SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ") AS totalTime"
+        };
+        String selection =
+                ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " != ? AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"IN_SERVICE", Integer.toString(mSlotIndex)};
+        String groupBy = ServiceStateAnalyticsTable.DEVICE_STATUS;
+        Cursor cursor = null;
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            ServiceStateAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            groupBy,
+                            null,
+                            null,
+                            null);
+
+            if (cursor != null) {
+                int reasonIndex = cursor.getColumnIndex(ServiceStateAnalyticsTable.DEVICE_STATUS);
+                int timeIndex = cursor.getColumnIndex("totalTime");
+
+                if (reasonIndex != -1 && timeIndex != -1) {
+                    while (cursor.moveToNext()) {
+                        String oosReason = cursor.getString(reasonIndex);
+                        long timeInterval = cursor.getLong(timeIndex);
+                        outOfServiceDurationByReason.put(oosReason, timeInterval);
+                    }
+                }
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return outOfServiceDurationByReason;
+    }
+
+    private HashMap<String, Long> getInServiceDurationByRat() {
+        HashMap<String, Long> inServiceDurationByRat = new HashMap<>();
+        String[] columns = {
+            ServiceStateAnalyticsTable.RAT,
+            "SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ") AS totalTime"
+        };
+        String selection =
+                ServiceStateAnalyticsTable.RAT
+                        + " != ? AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"NA", Integer.toString(mSlotIndex)};
+        String groupBy = ServiceStateAnalyticsTable.RAT;
+        Cursor cursor = null;
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            ServiceStateAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            groupBy,
+                            null,
+                            null,
+                            null);
+
+            if (cursor != null) {
+                int ratIndex = cursor.getColumnIndex(ServiceStateAnalyticsTable.RAT);
+                int timeIndex = cursor.getColumnIndex("totalTime");
+
+                if (ratIndex != -1 && timeIndex != -1) {
+                    while (cursor.moveToNext()) {
+                        String rat = cursor.getString(ratIndex);
+                        long timeInterval = cursor.getLong(timeIndex);
+                        inServiceDurationByRat.put(rat, timeInterval);
+                    }
+                }
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        return inServiceDurationByRat;
+    }
+
+    protected void deleteOldAndOverflowData() {
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        if (mDateOfDeletedRecordsServiceStateTable == null
+                || !mDateOfDeletedRecordsServiceStateTable.equals(dateToday)) {
+            mTelephonyAnalyticsUtil.deleteOverflowAndOldData(
+                    ServiceStateAnalyticsTable.TABLE_NAME,
+                    SERVICE_STATE_OVERFLOW_DATA_DELETION_SELECTION,
+                    SERVICE_STATE_OLD_DATA_DELETION_SELECTION);
+            mDateOfDeletedRecordsServiceStateTable = dateToday;
+        }
+    }
+
+    /** Setter function for mDateOfDeletedRecordsServiceStateTable */
+    public void setDateOfDeletedRecordsServiceStateTable(
+            String dateOfDeletedRecordsServiceStateTable) {
+        mDateOfDeletedRecordsServiceStateTable = dateOfDeletedRecordsServiceStateTable;
+    }
+
+    private ArrayList<String> dumpInformationInList(
+            Long upTime,
+            Long outOfServiceTime,
+            double percentageOutOfService,
+            HashMap<String, Long> oosByReason,
+            HashMap<String, Long> timeDurationByRat) {
+        ArrayList<String> aggregatedServiceStateInfo = new ArrayList<>();
+        aggregatedServiceStateInfo.add("Total UpTime = " + upTime + " millis");
+        aggregatedServiceStateInfo.add(
+                "Out of Service Time = "
+                        + outOfServiceTime
+                        + " millis, Percentage "
+                        + DECIMAL_FORMAT.format(percentageOutOfService)
+                        + "%");
+        oosByReason.forEach(
+                (k, v) -> {
+                    double percentageOosOfReason;
+                    if (upTime == 0) {
+                        percentageOosOfReason = 0.0;
+                    } else {
+                        percentageOosOfReason = (double) v / (double) upTime * 100.0;
+                    }
+                    aggregatedServiceStateInfo.add(
+                            "Out of service Reason = "
+                                    + k
+                                    + ", Percentage = "
+                                    + DECIMAL_FORMAT.format(percentageOosOfReason)
+                                    + "%");
+                });
+        timeDurationByRat.forEach(
+                (k, v) -> {
+                    double percentageInService;
+                    if (upTime == 0) {
+                        percentageInService = 0.0;
+                    } else {
+                        percentageInService = (double) v / (double) upTime * 100.0;
+                    }
+                    aggregatedServiceStateInfo.add(
+                            "IN_SERVICE RAT : "
+                                    + k
+                                    + ", Percentage = "
+                                    + DECIMAL_FORMAT.format(percentageInService)
+                                    + "%");
+                });
+        return aggregatedServiceStateInfo;
+    }
+
+    /**
+     * Collects all information which is intended to be a part of the report by calling the required
+     * functions implemented in the class.
+     *
+     * @return List which contains all the ServiceState related collected information.
+     */
+    public ArrayList<String> aggregate() {
+
+        long upTime = getTotalUpTime();
+        long outOfServiceTime = outOfServiceDuration();
+        double percentageOutOfService;
+        if (upTime == 0) {
+            percentageOutOfService = 0.0;
+        } else {
+            percentageOutOfService = (double) outOfServiceTime / upTime * 100.0;
+        }
+        HashMap<String, Long> oosByReason = getOutOfServiceDurationByReason();
+        HashMap<String, Long> timeDurationByRat = getInServiceDurationByRat();
+        return dumpInformationInList(
+                upTime, outOfServiceTime, percentageOutOfService, oosByReason, timeDurationByRat);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/analytics/SmsMmsAnalyticsProvider.java b/src/java/com/android/internal/telephony/analytics/SmsMmsAnalyticsProvider.java
new file mode 100644
index 0000000..e544765
--- /dev/null
+++ b/src/java/com/android/internal/telephony/analytics/SmsMmsAnalyticsProvider.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import static android.os.Build.VERSION.INCREMENTAL;
+
+import static com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.DATE_FORMAT;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable;
+import com.android.telephony.Rlog;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+
+/**
+ * Provider class for Sms and Mms Receives the data from SmsMmsAnalytics Performs business logic on
+ * the received data Uses Util class for required db operation. Implements the
+ * TelephonyAnalyticsProvider interface to provide aggregation functionality.
+ */
+public class SmsMmsAnalyticsProvider implements TelephonyAnalyticsProvider {
+    private static final String TAG = SmsMmsAnalyticsProvider.class.getSimpleName();
+
+    private TelephonyAnalyticsUtil mTelephonyAnalyticsUtil;
+    private String mDateOfDeletedRecordsSmsMmsTable;
+
+    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.00");
+    private static final String CREATE_SMS_MMS_ANALYTICS_TABLE =
+            "CREATE TABLE IF NOT EXISTS "
+                    + SmsMmsAnalyticsTable.TABLE_NAME
+                    + "("
+                    + SmsMmsAnalyticsTable._ID
+                    + " INTEGER PRIMARY KEY,"
+                    + SmsMmsAnalyticsTable.LOG_DATE
+                    + " DATE ,"
+                    + SmsMmsAnalyticsTable.SMS_MMS_STATUS
+                    + " TEXT DEFAULT '',"
+                    + SmsMmsAnalyticsTable.SMS_MMS_TYPE
+                    + " TEXT DEFAULT '',"
+                    + SmsMmsAnalyticsTable.SLOT_ID
+                    + " INTEGER , "
+                    + SmsMmsAnalyticsTable.RAT
+                    + " TEXT DEFAULT '',"
+                    + SmsMmsAnalyticsTable.FAILURE_REASON
+                    + " TEXT DEFAULT '',"
+                    + SmsMmsAnalyticsTable.RELEASE_VERSION
+                    + " TEXT DEFAULT '' , "
+                    + SmsMmsAnalyticsTable.COUNT
+                    + " INTEGER DEFAULT 1 "
+                    + ");";
+    private static final String SMS_MMS_OVERFLOW_DATA_DELETION_SELECTION =
+            SmsMmsAnalyticsTable._ID
+                    + " IN "
+                    + " ( SELECT "
+                    + SmsMmsAnalyticsTable._ID
+                    + " FROM "
+                    + SmsMmsAnalyticsTable.TABLE_NAME
+                    + " ORDER BY "
+                    + SmsMmsAnalyticsTable.LOG_DATE
+                    + " DESC LIMIT -1 OFFSET ? ) ";
+    private static final String SMS_MMS_OLD_DATA_DELETION_SELECTION =
+            SmsMmsAnalyticsTable.LOG_DATE + " < ? ";
+
+    private enum SmsMmsType {
+        SMS_OUTGOING("SMS Outgoing"),
+        SMS_INCOMING("SMS Incoming"),
+        MMS_OUTGOING("MMS Outgoing"),
+        MMS_INCOMING("MMS Incoming");
+        public final String value;
+
+        SmsMmsType(String value) {
+            this.value = value;
+        }
+    }
+
+    private enum SmsMmsStatus {
+        SUCCESS("Success"),
+        FAILURE("Failure");
+        public final String value;
+
+        SmsMmsStatus(String value) {
+            this.value = value;
+        }
+    }
+
+    private final int mSlotIndex;
+
+    public SmsMmsAnalyticsProvider(TelephonyAnalyticsUtil databaseUtil, int slotIndex) {
+        mTelephonyAnalyticsUtil = databaseUtil;
+        mSlotIndex = slotIndex;
+        mTelephonyAnalyticsUtil.createTable(CREATE_SMS_MMS_ANALYTICS_TABLE);
+    }
+
+    private static final String[] SMS_MMS_INSERTION_PROJECTION = {
+            SmsMmsAnalyticsTable._ID, SmsMmsAnalyticsTable.COUNT
+    };
+
+    private static final String SMS_MMS_INSERTION_SUCCESS_SELECTION =
+            SmsMmsAnalyticsTable.LOG_DATE
+                    + " = ? AND "
+                    + SmsMmsAnalyticsTable.SMS_MMS_TYPE
+                    + " = ? AND "
+                    + SmsMmsAnalyticsTable.SMS_MMS_STATUS
+                    + " = ? AND "
+                    + SmsMmsAnalyticsTable.SLOT_ID
+                    + " = ? ";
+
+    private static final String SMS_MMS_INSERTION_FAILURE_SELECTION =
+            SmsMmsAnalyticsTable.LOG_DATE
+                    + " = ? AND "
+                    + SmsMmsAnalyticsTable.SMS_MMS_STATUS
+                    + " = ? AND "
+                    + SmsMmsAnalyticsTable.SMS_MMS_TYPE
+                    + " = ? AND "
+                    + SmsMmsAnalyticsTable.RAT
+                    + " = ? AND "
+                    + SmsMmsAnalyticsTable.SLOT_ID
+                    + " = ? AND "
+                    + SmsMmsAnalyticsTable.FAILURE_REASON
+                    + " = ? AND "
+                    + SmsMmsAnalyticsTable.RELEASE_VERSION
+                    + " = ? ";
+
+    private ContentValues getContentValues(
+            String status, String smsMmsType, String rat, String failureReason) {
+        ContentValues values = new ContentValues();
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        values.put(SmsMmsAnalyticsTable.LOG_DATE, dateToday);
+        values.put(SmsMmsAnalyticsTable.SMS_MMS_STATUS, status);
+        values.put(SmsMmsAnalyticsTable.SMS_MMS_TYPE, smsMmsType);
+        values.put(SmsMmsAnalyticsTable.RAT, rat);
+        values.put(SmsMmsAnalyticsTable.SLOT_ID, mSlotIndex);
+        values.put(SmsMmsAnalyticsTable.FAILURE_REASON, failureReason);
+        values.put(SmsMmsAnalyticsTable.RELEASE_VERSION, INCREMENTAL);
+        return values;
+    }
+
+    /**
+     * Processes the received data for insertion to the database.
+     *
+     * @param status : SMS Status ,i.e. Success or Failure
+     * @param smsMmsType : Type ,i.e. outgoing/incoming
+     * @param rat : Radio Access Technology
+     * @param failureReason : Reason for failure
+     */
+    @VisibleForTesting
+    public void insertDataToDb(String status, String smsMmsType, String rat, String failureReason) {
+        ContentValues values = getContentValues(status, smsMmsType, rat, failureReason);
+        Rlog.d(TAG, values.toString());
+        Cursor cursor = null;
+        String[] selectionArgs;
+        try {
+            if (values.getAsString(SmsMmsAnalyticsTable.SMS_MMS_STATUS)
+                    .equals(SmsMmsStatus.SUCCESS.value)) {
+                Rlog.d(TAG, "Success Entry Data for Sms/Mms: " + values.toString());
+                selectionArgs =
+                        new String[] {
+                                values.getAsString(SmsMmsAnalyticsTable.LOG_DATE),
+                                values.getAsString(SmsMmsAnalyticsTable.SMS_MMS_TYPE),
+                                values.getAsString(SmsMmsAnalyticsTable.SMS_MMS_STATUS),
+                                values.getAsString(SmsMmsAnalyticsTable.SLOT_ID)
+                        };
+                cursor =
+                        mTelephonyAnalyticsUtil.getCursor(
+                                SmsMmsAnalyticsTable.TABLE_NAME,
+                                SMS_MMS_INSERTION_PROJECTION,
+                                SMS_MMS_INSERTION_SUCCESS_SELECTION,
+                                selectionArgs,
+                                null,
+                                null,
+                                null,
+                                null);
+
+            } else {
+                selectionArgs =
+                        new String[] {
+                                values.getAsString(SmsMmsAnalyticsTable.LOG_DATE),
+                                values.getAsString(SmsMmsAnalyticsTable.SMS_MMS_STATUS),
+                                values.getAsString(SmsMmsAnalyticsTable.SMS_MMS_TYPE),
+                                values.getAsString(SmsMmsAnalyticsTable.RAT),
+                                values.getAsString(SmsMmsAnalyticsTable.SLOT_ID),
+                                values.getAsString(SmsMmsAnalyticsTable.FAILURE_REASON),
+                                values.getAsString(SmsMmsAnalyticsTable.RELEASE_VERSION)
+                        };
+                cursor =
+                        mTelephonyAnalyticsUtil.getCursor(
+                                SmsMmsAnalyticsTable.TABLE_NAME,
+                                SMS_MMS_INSERTION_PROJECTION,
+                                SMS_MMS_INSERTION_FAILURE_SELECTION,
+                                selectionArgs,
+                                null,
+                                null,
+                                null,
+                                null);
+            }
+            updateIfEntryExistsOtherwiseInsert(cursor, values);
+            deleteOldAndOverflowData();
+        } catch (Exception e) {
+            Rlog.e(TAG, "Exception during Sms/Mms Insertion [insertDataToDb()] " + e);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+    }
+
+    /**
+     * Checks if a similar entry exists and updates the existing entry. Otherwise, inserts a new
+     * entry.
+     */
+    @VisibleForTesting
+    public void updateIfEntryExistsOtherwiseInsert(Cursor cursor, ContentValues values) {
+        if (cursor != null && cursor.moveToFirst()) {
+            int idColumnIndex = cursor.getColumnIndex(SmsMmsAnalyticsTable._ID);
+            int countColumnIndex = cursor.getColumnIndex(SmsMmsAnalyticsTable.COUNT);
+            if (idColumnIndex != -1 && countColumnIndex != -1) {
+                int id = cursor.getInt(idColumnIndex);
+                int count = cursor.getInt(countColumnIndex);
+                int newCount = count + 1;
+
+                values.put(SmsMmsAnalyticsTable.COUNT, newCount);
+
+                String updateSelection = SmsMmsAnalyticsTable._ID + " = ? ";
+                String[] updateSelectionArgs = {String.valueOf(id)};
+                Rlog.d(TAG, "Updated Count = " + values.getAsString(SmsMmsAnalyticsTable.COUNT));
+
+                mTelephonyAnalyticsUtil.update(
+                        SmsMmsAnalyticsTable.TABLE_NAME,
+                        values,
+                        updateSelection,
+                        updateSelectionArgs);
+            }
+        } else {
+            mTelephonyAnalyticsUtil.insert(SmsMmsAnalyticsTable.TABLE_NAME, values);
+        }
+    }
+
+    /** Gets the count from the cursor */
+    @VisibleForTesting
+    public long getCount(String[] columns, String selection, String[] selectionArgs) {
+        Cursor cursor = null;
+        long count = 0;
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            SmsMmsAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            null,
+                            null,
+                            null,
+                            null);
+            count = mTelephonyAnalyticsUtil.getCountFromCursor(cursor);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return count;
+    }
+
+    private long getSmsMmsOfTypeAndStatus(String smsMmsType, String smsMmsStatus) {
+        String[] columns = {"SUM(" + SmsMmsAnalyticsTable.COUNT + ")"};
+        String selection = SmsMmsAnalyticsTable.SLOT_ID + " = ? ";
+        String[] selectionArgs = {Integer.toString(mSlotIndex)};
+
+        if (smsMmsType != null && smsMmsStatus != null) {
+            selection =
+                    SmsMmsAnalyticsTable.SMS_MMS_TYPE
+                            + " = ? AND "
+                            + SmsMmsAnalyticsTable.SMS_MMS_STATUS
+                            + " = ? AND "
+                            + SmsMmsAnalyticsTable.SLOT_ID
+                            + " = ? ";
+            selectionArgs = new String[] {smsMmsType, smsMmsStatus, Integer.toString(mSlotIndex)};
+        } else if (smsMmsType != null) {
+            selection =
+                    SmsMmsAnalyticsTable.SMS_MMS_TYPE
+                            + " = ? AND "
+                            + SmsMmsAnalyticsTable.SLOT_ID
+                            + " = ? ";
+            selectionArgs = new String[] {smsMmsType, Integer.toString(mSlotIndex)};
+        } else if (smsMmsStatus != null) {
+            selection =
+                    SmsMmsAnalyticsTable.SMS_MMS_STATUS
+                            + " = ? AND "
+                            + SmsMmsAnalyticsTable.SLOT_ID
+                            + " = ? ";
+            selectionArgs = new String[] {smsMmsStatus, Integer.toString(mSlotIndex)};
+        }
+        return getCount(columns, selection, selectionArgs);
+    }
+
+    private long getSmsOutgoingCount() {
+        return getSmsMmsOfTypeAndStatus(SmsMmsType.SMS_OUTGOING.value, null);
+    }
+
+    private long getSmsIncomingCount() {
+        return getSmsMmsOfTypeAndStatus(SmsMmsType.SMS_INCOMING.value, null);
+    }
+
+    private long getMmsOutgoingCount() {
+        return getSmsMmsOfTypeAndStatus(SmsMmsType.MMS_OUTGOING.value, null);
+    }
+
+    private long getMmsIncomingCount() {
+        return getSmsMmsOfTypeAndStatus(SmsMmsType.MMS_INCOMING.value, null);
+    }
+
+    private long getFailedOutgoingSms() {
+        return getSmsMmsOfTypeAndStatus(SmsMmsType.SMS_OUTGOING.value, SmsMmsStatus.FAILURE.value);
+    }
+
+    private long getFailedIncomingSms() {
+        return getSmsMmsOfTypeAndStatus(SmsMmsType.SMS_INCOMING.value, SmsMmsStatus.FAILURE.value);
+    }
+
+    private long getFailedOutgoingMms() {
+        return getSmsMmsOfTypeAndStatus(SmsMmsType.MMS_OUTGOING.value, SmsMmsStatus.FAILURE.value);
+    }
+
+    private long getFailedIncomingMms() {
+        return getSmsMmsOfTypeAndStatus(SmsMmsType.MMS_INCOMING.value, SmsMmsStatus.FAILURE.value);
+    }
+
+    private HashMap<String, Integer> getSmsFailedCountByRat() {
+        HashMap<String, Integer> failedSmsTypeCountByRat = new HashMap<>();
+        String[] columns = {
+                SmsMmsAnalyticsTable.RAT, "SUM(" + SmsMmsAnalyticsTable.COUNT + ") as count "
+        };
+        String selection =
+                SmsMmsAnalyticsTable.SLOT_ID
+                        + " = ? AND "
+                        + SmsMmsAnalyticsTable.SMS_MMS_STATUS
+                        + " = ? AND "
+                        + SmsMmsAnalyticsTable.SMS_MMS_TYPE
+                        + " IN ('"
+                        + SmsMmsType.SMS_INCOMING.value
+                        + "', '"
+                        + SmsMmsType.SMS_OUTGOING.value
+                        + "' ) ";
+        String[] selectionArgs = {Integer.toString(mSlotIndex), SmsMmsStatus.FAILURE.value};
+        String groupBy = SmsMmsAnalyticsTable.RAT;
+        Cursor cursor = null;
+        try {
+            cursor =
+                    mTelephonyAnalyticsUtil.getCursor(
+                            SmsMmsAnalyticsTable.TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            groupBy,
+                            null,
+                            null,
+                            null);
+            if (cursor != null) {
+                int ratIndex = cursor.getColumnIndex(SmsMmsAnalyticsTable.RAT);
+                int countIndex = cursor.getColumnIndex("count");
+
+                if (ratIndex != -1 && countIndex != -1) {
+                    while (cursor.moveToNext()) {
+                        String rat = cursor.getString(ratIndex);
+                        int count = cursor.getInt(countIndex);
+                        failedSmsTypeCountByRat.put(rat, count);
+                    }
+                }
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return failedSmsTypeCountByRat;
+    }
+
+    protected void deleteOldAndOverflowData() {
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        if (mDateOfDeletedRecordsSmsMmsTable == null
+                || !mDateOfDeletedRecordsSmsMmsTable.equals(dateToday)) {
+            mTelephonyAnalyticsUtil.deleteOverflowAndOldData(
+                    SmsMmsAnalyticsTable.TABLE_NAME,
+                    SMS_MMS_OVERFLOW_DATA_DELETION_SELECTION,
+                    SMS_MMS_OLD_DATA_DELETION_SELECTION);
+            mDateOfDeletedRecordsSmsMmsTable = dateToday;
+        }
+    }
+
+    /** Setter function for mDateOfDeletedRecordsSmsMmsTable for testing purposes */
+    public void setDateOfDeletedRecordsSmsMmsTable(String deletedDate) {
+        mDateOfDeletedRecordsSmsMmsTable = deletedDate;
+    }
+
+    /**
+     * Sets all the received information in a list along with the associated String description.
+     *
+     * @return A list of all the information with their respective description.
+     */
+    @VisibleForTesting
+    public ArrayList<String> dumpInformationInList(
+            long totalOutgoingSms,
+            long totalIncomingSms,
+            long failedOutgoingSms,
+            long failedIncomingSms,
+            long totalOutgoingMms,
+            long totalIncomingMms,
+            double percentageFailureOutgoingSms,
+            double percentageFailureIncomingSms,
+            double percentageFailureOutgoingMms,
+            double percentageFailureIncomingMms,
+            HashMap<String, Integer> failedSmsTypeCountByRat) {
+
+        ArrayList<String> aggregatedSmsMmsInformation = new ArrayList<>();
+        aggregatedSmsMmsInformation.add("Total Outgoing Sms Count = " + totalOutgoingSms);
+        aggregatedSmsMmsInformation.add("Total Incoming Sms Count = " + totalIncomingSms);
+        aggregatedSmsMmsInformation.add(
+                "Failed Outgoing SMS Count = "
+                        + failedOutgoingSms
+                        + " Percentage failure rate for Outgoing SMS :"
+                        + DECIMAL_FORMAT.format(percentageFailureOutgoingSms)
+                        + "%");
+        aggregatedSmsMmsInformation.add(
+                "Failed Incoming SMS Count = "
+                        + failedIncomingSms
+                        + " Percentage failure rate for Incoming SMS :"
+                        + DECIMAL_FORMAT.format(percentageFailureIncomingSms)
+                        + "%");
+
+        double overallFailPercentage =
+                (double) (failedIncomingSms + failedOutgoingSms)
+                        / (double) (totalIncomingSms + totalOutgoingSms)
+                        * 100.0;
+        aggregatedSmsMmsInformation.add(
+                "Overall Fail Percentage = " + DECIMAL_FORMAT.format(overallFailPercentage) + "%");
+        try {
+            failedSmsTypeCountByRat.forEach(
+                    (k, v) -> {
+                        double percentageFail =
+                                ((double) v
+                                        / (double) (totalIncomingSms + totalOutgoingSms)
+                                        * 100.0);
+                        aggregatedSmsMmsInformation.add(
+                                "Failed SMS Count for RAT : "
+                                        + k
+                                        + " = "
+                                        + v
+                                        + ", Percentage = "
+                                        + DECIMAL_FORMAT.format(percentageFail)
+                                        + "%");
+                    });
+
+        } catch (Exception e) {
+            Rlog.d(TAG, "Exception in adding to List = " + e);
+        }
+
+        return aggregatedSmsMmsInformation;
+    }
+
+    /**
+     * Gathers all the necessary information for the report by using specific methods.
+     *
+     * @return List of SmsMms analytics information.
+     */
+    public ArrayList<String> aggregate() {
+        long totalOutgoingSms = getSmsOutgoingCount();
+        long totalIncomingSms = getSmsIncomingCount();
+        long totalOutgoingMms = getMmsOutgoingCount();
+        long totalIncomingMms = getMmsIncomingCount();
+        long totalFailedOutgoingSms = getFailedOutgoingSms();
+        long totalFailedIncomingSms = getFailedIncomingSms();
+        long totalFailedOutgoingMms = getFailedOutgoingMms();
+        long totalFailedIncomingMms = getFailedIncomingMms();
+        HashMap<String, Integer> failedSmsTypeCountByRat = getSmsFailedCountByRat();
+
+        double percentageFailureOutgoingSms =
+                totalOutgoingSms != 0
+                        ? (double) totalFailedOutgoingSms / totalOutgoingSms * 100.0
+                        : 0;
+        double percentageFailureIncomingSms =
+                totalIncomingSms != 0
+                        ? (double) totalFailedIncomingSms / totalIncomingSms * 100.0
+                        : 0;
+
+        double percentageFailureOutgoingMms =
+                totalOutgoingMms != 0
+                        ? (double) totalFailedOutgoingMms / totalOutgoingMms * 100.0
+                        : 0;
+        double percentageFailureIncomingMms =
+                totalIncomingMms != 0
+                        ? (double) totalFailedIncomingMms / totalIncomingMms * 100.0
+                        : 0;
+
+        return dumpInformationInList(
+                totalOutgoingSms,
+                totalIncomingSms,
+                totalFailedOutgoingSms,
+                totalFailedIncomingSms,
+                totalOutgoingMms,
+                totalIncomingMms,
+                percentageFailureOutgoingSms,
+                percentageFailureIncomingSms,
+                percentageFailureOutgoingMms,
+                percentageFailureIncomingMms,
+                failedSmsTypeCountByRat);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java b/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java
new file mode 100644
index 0000000..e74e40e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java
@@ -0,0 +1,1204 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import static com.android.internal.telephony.InboundSmsHandler.SOURCE_INJECTED_FROM_IMS;
+import static com.android.internal.telephony.InboundSmsHandler.SOURCE_INJECTED_FROM_UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_NOT_SUPPORTED;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_NO_MEMORY;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_SUCCESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.Annotation;
+import android.telephony.DisconnectCause;
+import android.telephony.ServiceState;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+
+import com.android.internal.telephony.InboundSmsHandler;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Class to handle all telephony analytics related operations Initializes all the Analytics,
+ * Provider and Util classes. Registers the required Callbacks for supporting the
+ * ServiceStateAnalytics , SMS Analytics and Call Analytics
+ */
+public class TelephonyAnalytics {
+    private static final String TAG = TelephonyAnalytics.class.getSimpleName();
+    protected static final int INVALID_SUB_ID = -1;
+    private final int mSlotIndex;
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private ExecutorService mExecutorService;
+    protected TelephonyAnalyticsUtil mTelephonyAnalyticsUtil;
+    protected int mSubId;
+    protected ServiceStateAnalytics mServiceStateAnalytics;
+    protected Context mContext;
+    protected Executor mExecutor;
+    protected SubscriptionManager mSubscriptionManager;
+    protected final SubscriptionManager.OnSubscriptionsChangedListener
+            mSubscriptionsChangeListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+                @Override
+                public void onSubscriptionsChanged() {
+                    int newSubId = getSubId();
+                    if ((mSubId != newSubId)
+                            && (newSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID)) {
+                        stopAnalytics(mSubId);
+                        mSubId = newSubId;
+                        startAnalytics(newSubId);
+                        Rlog.d(
+                                TAG,
+                                "Started Listener, mSubId = "
+                                        + mSubId
+                                        + "SlotId = "
+                                        + mSlotIndex);
+                    }
+                }
+            };
+    protected CallAnalyticsProvider mCallAnalyticsProvider;
+    protected SmsMmsAnalyticsProvider mSmsMmsAnalyticsProvider;
+    protected ServiceStateAnalyticsProvider mServiceStateAnalyticsProvider;
+    protected SmsMmsAnalytics mSmsMmsAnalytics;
+    protected CallAnalytics mCallAnalytics;
+    protected Phone mPhone;
+
+    public TelephonyAnalytics(Phone phone) {
+        mPhone = phone;
+        mContext = mPhone.getContext();
+        mExecutor = Runnable::run;
+        mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
+        mSlotIndex = mPhone.getPhoneId();
+
+        mHandlerThread = new HandlerThread(TelephonyAnalytics.class.getSimpleName());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mExecutorService = Executors.newSingleThreadExecutor();
+        mTelephonyAnalyticsUtil = TelephonyAnalyticsUtil.getInstance(mContext);
+        initializeAnalyticsClasses();
+        mCallAnalyticsProvider = new CallAnalyticsProvider(mTelephonyAnalyticsUtil, mSlotIndex);
+        mSmsMmsAnalyticsProvider = new SmsMmsAnalyticsProvider(mTelephonyAnalyticsUtil, mSlotIndex);
+        mServiceStateAnalyticsProvider =
+                new ServiceStateAnalyticsProvider(mTelephonyAnalyticsUtil, mSlotIndex);
+
+        startAnalytics(mSubId);
+
+        if (mSubscriptionManager != null) {
+            mSubscriptionManager.addOnSubscriptionsChangedListener(
+                    mExecutor, mSubscriptionsChangeListener);
+            Rlog.d(TAG, "stopped listener");
+        }
+    }
+
+    @SuppressLint("MissingPermission")
+    private int getSubId() {
+        int subId = INVALID_SUB_ID;
+        try {
+            SubscriptionInfo info =
+                    mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(mSlotIndex);
+            subId = info.getSubscriptionId();
+            Rlog.d("TelephonyAnalyticsSubId", "SubId = " + subId
+                    + "SlotIndex = " + mSlotIndex);
+        } catch (NullPointerException e) {
+            Rlog.e("TelephonyAnalyticsSubId", "Null Pointer Exception Caught");
+        }
+        return subId;
+    }
+
+    private void initializeAnalyticsClasses() {
+        mServiceStateAnalytics = new ServiceStateAnalytics(mExecutor);
+        mSmsMmsAnalytics = new SmsMmsAnalytics();
+        mCallAnalytics = new CallAnalytics();
+    }
+
+    protected void startAnalytics(int subId) {
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            Rlog.d(
+                    "StartAnalytics",
+                    "Invalid SubId = " + SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            return;
+        }
+        mServiceStateAnalytics.registerMyListener(mContext, subId);
+    }
+
+    protected void stopAnalytics(int subId) {
+        if (mServiceStateAnalytics != null) {
+            mServiceStateAnalytics.unregisterMyListener(subId);
+        }
+    }
+
+    public SmsMmsAnalytics getSmsMmsAnalytics() {
+        return mSmsMmsAnalytics;
+    }
+
+    public CallAnalytics getCallAnalytics() {
+        return mCallAnalytics;
+    }
+
+    /**
+     * Uses the provider class objects,collects the aggregated report from the respective provider
+     * classes. Dumps the collected stats in the bugreport.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
+        pw.println("+    Telephony Analytics Report [2 months] [Slot ID = " + mSlotIndex + "]  +");
+        pw.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
+        pw.println("Call Analytics Summary");
+        ArrayList<String> aggregatedCallInfo = mCallAnalyticsProvider.aggregate();
+        for (String info : aggregatedCallInfo) {
+            pw.println("\t" + info);
+        }
+        pw.println("-----------------------------------------------");
+        pw.println("SMS/MMS Analytics Summary");
+        ArrayList<String> aggregatedSmsMmsInfo = mSmsMmsAnalyticsProvider.aggregate();
+        for (String info : aggregatedSmsMmsInfo) {
+            pw.println("\t\t" + info);
+        }
+        pw.println("-----------------------------------------------");
+        mServiceStateAnalytics.recordCurrentStateBeforeDump();
+        pw.println("Service State Analytics Summary ");
+        ArrayList<String> aggregatedServiceStateInfo = mServiceStateAnalyticsProvider.aggregate();
+        for (String info : aggregatedServiceStateInfo) {
+            pw.println("\t\t" + info);
+        }
+        pw.println("-----------------------------------------------");
+    }
+
+    /**
+     * Provides implementation for processing received Call related data. It implements functions to
+     * handle various scenarios pertaining to Calls. Passes the data to its provider class
+     * for further processing.
+     */
+    public class CallAnalytics {
+        private static final String TAG = CallAnalytics.class.getSimpleName();
+
+        private enum Status {
+            SUCCESS("Success"),
+            FAILURE("Failure");
+            public String value;
+
+            Status(String value) {
+                this.value = value;
+            }
+        }
+
+        private enum CallType {
+            NORMAL_CALL("Normal Call"),
+            SOS_CALL("SOS Call");
+            public String value;
+
+            CallType(String value) {
+                this.value = value;
+            }
+        }
+
+        public CallAnalytics() {}
+
+        /**
+         * Collects and processes data related to calls once the call is terminated.
+         *
+         * @param isEmergency : Stores whether the call is an SOS call or not
+         * @param isOverIms : Stores whether the call is over IMS.
+         * @param rat : Stores the Radio Access Technology being used when the call ended
+         * @param simSlotIndex : Sim Slot from which call was operating.
+         * @param disconnectCause : Reason for call disconnect.
+         */
+        public void onCallTerminated(
+                boolean isEmergency,
+                boolean isOverIms,
+                int rat,
+                int simSlotIndex,
+                int disconnectCause) {
+            String disconnectCauseString;
+            String status;
+            String callType;
+            if (isEmergency) {
+                callType = CallType.SOS_CALL.value;
+            } else {
+                callType = CallType.NORMAL_CALL.value;
+            }
+            if (isOverIms) {
+                disconnectCauseString =
+                        sImsCodeMap.getOrDefault(disconnectCause, "UNKNOWN_REJECT_CAUSE");
+                if (disconnectCauseString.equals("UNKNOWN_REJECT_CAUSE")) {
+                    Rlog.d(TAG, "UNKNOWN_REJECT_CAUSE: " + disconnectCause);
+                }
+                status =
+                        disconnectCause == ImsReasonInfo.CODE_USER_TERMINATED
+                                || disconnectCause
+                                == ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE
+                                ? Status.SUCCESS.value
+                                : Status.FAILURE.value;
+            } else {
+                disconnectCauseString = DisconnectCause.toString(disconnectCause);
+                status =
+                        disconnectCause == DisconnectCause.LOCAL
+                                || disconnectCause == DisconnectCause.NORMAL
+                                ? Status.SUCCESS.value
+                                : Status.FAILURE.value;
+            }
+            String ratString = TelephonyManager.getNetworkTypeName(rat);
+            sendDataToProvider(callType, status, simSlotIndex, rat, ratString,
+                    disconnectCause, disconnectCauseString);
+        }
+
+        private void sendDataToProvider(String callType, String status, int simSlotIndex,
+                int rat, String ratString, int disconnectCause, String disconnectCauseString) {
+            mExecutorService.execute(() -> {
+                mCallAnalyticsProvider.insertDataToDb(
+                        callType, status, simSlotIndex, ratString, disconnectCauseString);
+                ArrayList<String> data;
+                data =
+                        new ArrayList<>(
+                                List.of(
+                                        callType,
+                                        status,
+                                        disconnectCauseString,
+                                        "(" + disconnectCause + ")",
+                                        ratString,
+                                        "(" + rat + ")",
+                                        Integer.toString(simSlotIndex)));
+                Rlog.d(TAG, data.toString());
+            });
+        }
+
+        private static final Map<Integer, String> sImsCodeMap;
+
+        static {
+            sImsCodeMap = new HashMap<>();
+            sImsCodeMap.put(ImsReasonInfo.CODE_UNSPECIFIED, "CODE_UNSPECIFIED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT, "CODE_LOCAL_ILLEGAL_ARGUMENT");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE, "CODE_LOCAL_ILLEGAL_STATE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR, "CODE_LOCAL_INTERNAL_ERROR");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN, "CODE_LOCAL_IMS_SERVICE_DOWN");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL, "CODE_LOCAL_NO_PENDING_CALL");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE,
+                    "CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_POWER_OFF, "CODE_LOCAL_POWER_OFF");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY, "CODE_LOCAL_LOW_BATTERY");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE, "CODE_LOCAL_NETWORK_NO_SERVICE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE,
+                    "CODE_LOCAL_NETWORK_NO_LTE_COVERAGE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING, "CODE_LOCAL_NETWORK_ROAMING");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED, "CODE_LOCAL_NETWORK_IP_CHANGED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE, "CODE_LOCAL_SERVICE_UNAVAILABLE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED, "CODE_LOCAL_NOT_REGISTERED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_CALL_EXCEEDED, "CODE_LOCAL_CALL_EXCEEDED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_CALL_BUSY, "CODE_LOCAL_CALL_BUSY");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, "CODE_LOCAL_CALL_DECLINE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING,
+                    "CODE_LOCAL_CALL_VCC_ON_PROGRESSING");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED,
+                    "CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED,
+                    "CODE_LOCAL_CALL_CS_RETRY_REQUIRED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED,
+                    "CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED, "CODE_LOCAL_CALL_TERMINATED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOCAL_HO_NOT_FEASIBLE, "CODE_LOCAL_HO_NOT_FEASIBLE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING, "CODE_TIMEOUT_1XX_WAITING");
+            sImsCodeMap.put(ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER, "CODE_TIMEOUT_NO_ANSWER");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE,
+                    "CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_CALL_BARRED, "CODE_CALL_BARRED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_FDN_BLOCKED, "CODE_FDN_BLOCKED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_IMEI_NOT_ACCEPTED, "CODE_IMEI_NOT_ACCEPTED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_DIAL_MODIFIED_TO_USSD, "CODE_DIAL_MODIFIED_TO_USSD");
+            sImsCodeMap.put(ImsReasonInfo.CODE_DIAL_MODIFIED_TO_SS, "CODE_DIAL_MODIFIED_TO_SS");
+            sImsCodeMap.put(ImsReasonInfo.CODE_DIAL_MODIFIED_TO_DIAL, "CODE_DIAL_MODIFIED_TO_DIAL");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_DIAL_MODIFIED_TO_DIAL_VIDEO,
+                    "CODE_DIAL_MODIFIED_TO_DIAL_VIDEO");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_DIAL_VIDEO_MODIFIED_TO_DIAL,
+                    "CODE_DIAL_VIDEO_MODIFIED_TO_DIAL");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO,
+                    "CODE_DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_DIAL_VIDEO_MODIFIED_TO_SS, "CODE_DIAL_VIDEO_MODIFIED_TO_SS");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_DIAL_VIDEO_MODIFIED_TO_USSD,
+                    "CODE_DIAL_VIDEO_MODIFIED_TO_USSD");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_REDIRECTED, "CODE_SIP_REDIRECTED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_BAD_REQUEST, "CODE_SIP_BAD_REQUEST");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_FORBIDDEN, "CODE_SIP_FORBIDDEN");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_NOT_FOUND, "CODE_SIP_NOT_FOUND");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_NOT_SUPPORTED, "CODE_SIP_NOT_SUPPORTED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT, "CODE_SIP_REQUEST_TIMEOUT");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_TEMPRARILY_UNAVAILABLE,
+                    "CODE_SIP_TEMPRARILY_UNAVAILABLE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_BAD_ADDRESS, "CODE_SIP_BAD_ADDRESS");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_BUSY, "CODE_SIP_BUSY");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_REQUEST_CANCELLED, "CODE_SIP_REQUEST_CANCELLED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE, "CODE_SIP_NOT_ACCEPTABLE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_NOT_REACHABLE, "CODE_SIP_NOT_REACHABLE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_CLIENT_ERROR, "CODE_SIP_CLIENT_ERROR");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_TRANSACTION_DOES_NOT_EXIST,
+                    "CODE_SIP_TRANSACTION_DOES_NOT_EXIST");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_SERVER_INTERNAL_ERROR, "CODE_SIP_SERVER_INTERNAL_ERROR");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE, "CODE_SIP_SERVICE_UNAVAILABLE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_SERVER_TIMEOUT, "CODE_SIP_SERVER_TIMEOUT");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_SERVER_ERROR, "CODE_SIP_SERVER_ERROR");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_USER_REJECTED, "CODE_SIP_USER_REJECTED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_GLOBAL_ERROR, "CODE_SIP_GLOBAL_ERROR");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_EMERGENCY_TEMP_FAILURE, "CODE_EMERGENCY_TEMP_FAILURE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_EMERGENCY_PERM_FAILURE, "CODE_EMERGENCY_PERM_FAILURE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_USER_MARKED_UNWANTED, "CODE_SIP_USER_MARKED_UNWANTED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_METHOD_NOT_ALLOWED, "CODE_SIP_METHOD_NOT_ALLOWED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_PROXY_AUTHENTICATION_REQUIRED,
+                    "CODE_SIP_PROXY_AUTHENTICATION_REQUIRED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_REQUEST_ENTITY_TOO_LARGE,
+                    "CODE_SIP_REQUEST_ENTITY_TOO_LARGE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_REQUEST_URI_TOO_LARGE, "CODE_SIP_REQUEST_URI_TOO_LARGE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_EXTENSION_REQUIRED, "CODE_SIP_EXTENSION_REQUIRED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_INTERVAL_TOO_BRIEF, "CODE_SIP_INTERVAL_TOO_BRIEF");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_CALL_OR_TRANS_DOES_NOT_EXIST,
+                    "CODE_SIP_CALL_OR_TRANS_DOES_NOT_EXIST");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_LOOP_DETECTED, "CODE_SIP_LOOP_DETECTED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_TOO_MANY_HOPS, "CODE_SIP_TOO_MANY_HOPS");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_AMBIGUOUS, "CODE_SIP_AMBIGUOUS");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_REQUEST_PENDING, "CODE_SIP_REQUEST_PENDING");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SIP_UNDECIPHERABLE, "CODE_SIP_UNDECIPHERABLE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_MEDIA_INIT_FAILED, "CODE_MEDIA_INIT_FAILED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_MEDIA_NO_DATA, "CODE_MEDIA_NO_DATA");
+            sImsCodeMap.put(ImsReasonInfo.CODE_MEDIA_NOT_ACCEPTABLE, "CODE_MEDIA_NOT_ACCEPTABLE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_MEDIA_UNSPECIFIED, "CODE_MEDIA_UNSPECIFIED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_USER_TERMINATED, "CODE_USER_TERMINATED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_USER_NOANSWER, "CODE_USER_NOANSWER");
+            sImsCodeMap.put(ImsReasonInfo.CODE_USER_IGNORE, "CODE_USER_IGNORE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_USER_DECLINE, "CODE_USER_DECLINE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_LOW_BATTERY, "CODE_LOW_BATTERY");
+            sImsCodeMap.put(ImsReasonInfo.CODE_BLACKLISTED_CALL_ID, "CODE_BLACKLISTED_CALL_ID");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, "CODE_USER_TERMINATED_BY_REMOTE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_USER_REJECTED_SESSION_MODIFICATION,
+                    "CODE_USER_REJECTED_SESSION_MODIFICATION");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_USER_CANCELLED_SESSION_MODIFICATION,
+                    "CODE_USER_CANCELLED_SESSION_MODIFICATION");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SESSION_MODIFICATION_FAILED,
+                    "CODE_SESSION_MODIFICATION_FAILED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_UT_NOT_SUPPORTED, "CODE_UT_NOT_SUPPORTED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE, "CODE_UT_SERVICE_UNAVAILABLE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_UT_OPERATION_NOT_ALLOWED, "CODE_UT_OPERATION_NOT_ALLOWED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_UT_NETWORK_ERROR, "CODE_UT_NETWORK_ERROR");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_UT_CB_PASSWORD_MISMATCH, "CODE_UT_CB_PASSWORD_MISMATCH");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL, "CODE_UT_SS_MODIFIED_TO_DIAL");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_USSD, "CODE_UT_SS_MODIFIED_TO_USSD");
+            sImsCodeMap.put(ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_SS, "CODE_UT_SS_MODIFIED_TO_SS");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO,
+                    "CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO");
+            sImsCodeMap.put(ImsReasonInfo.CODE_ECBM_NOT_SUPPORTED, "CODE_ECBM_NOT_SUPPORTED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_MULTIENDPOINT_NOT_SUPPORTED,
+                    "CODE_MULTIENDPOINT_NOT_SUPPORTED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_REGISTRATION_ERROR, "CODE_REGISTRATION_ERROR");
+            sImsCodeMap.put(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE, "CODE_ANSWERED_ELSEWHERE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_CALL_PULL_OUT_OF_SYNC, "CODE_CALL_PULL_OUT_OF_SYNC");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_CALL_END_CAUSE_CALL_PULL, "CODE_CALL_END_CAUSE_CALL_PULL");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE,
+                    "CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_REJECTED_ELSEWHERE, "CODE_REJECTED_ELSEWHERE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SUPP_SVC_FAILED, "CODE_SUPP_SVC_FAILED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_SUPP_SVC_CANCELLED, "CODE_SUPP_SVC_CANCELLED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SUPP_SVC_REINVITE_COLLISION,
+                    "CODE_SUPP_SVC_REINVITE_COLLISION");
+            sImsCodeMap.put(ImsReasonInfo.CODE_IWLAN_DPD_FAILURE, "CODE_IWLAN_DPD_FAILURE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_EPDG_TUNNEL_ESTABLISH_FAILURE,
+                    "CODE_EPDG_TUNNEL_ESTABLISH_FAILURE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_EPDG_TUNNEL_REKEY_FAILURE, "CODE_EPDG_TUNNEL_REKEY_FAILURE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_EPDG_TUNNEL_LOST_CONNECTION,
+                    "CODE_EPDG_TUNNEL_LOST_CONNECTION");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED,
+                    "CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_REMOTE_CALL_DECLINE, "CODE_REMOTE_CALL_DECLINE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_DATA_LIMIT_REACHED, "CODE_DATA_LIMIT_REACHED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_DATA_DISABLED, "CODE_DATA_DISABLED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_WIFI_LOST, "CODE_WIFI_LOST");
+            sImsCodeMap.put(ImsReasonInfo.CODE_IKEV2_AUTH_FAILURE, "CODE_IKEV2_AUTH_FAILURE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_RADIO_OFF, "CODE_RADIO_OFF");
+            sImsCodeMap.put(ImsReasonInfo.CODE_NO_VALID_SIM, "CODE_NO_VALID_SIM");
+            sImsCodeMap.put(ImsReasonInfo.CODE_RADIO_INTERNAL_ERROR, "CODE_RADIO_INTERNAL_ERROR");
+            sImsCodeMap.put(ImsReasonInfo.CODE_NETWORK_RESP_TIMEOUT, "CODE_NETWORK_RESP_TIMEOUT");
+            sImsCodeMap.put(ImsReasonInfo.CODE_NETWORK_REJECT, "CODE_NETWORK_REJECT");
+            sImsCodeMap.put(ImsReasonInfo.CODE_RADIO_ACCESS_FAILURE, "CODE_RADIO_ACCESS_FAILURE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_RADIO_LINK_FAILURE, "CODE_RADIO_LINK_FAILURE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_RADIO_LINK_LOST, "CODE_RADIO_LINK_LOST");
+            sImsCodeMap.put(ImsReasonInfo.CODE_RADIO_UPLINK_FAILURE, "CODE_RADIO_UPLINK_FAILURE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_RADIO_SETUP_FAILURE, "CODE_RADIO_SETUP_FAILURE");
+            sImsCodeMap.put(ImsReasonInfo.CODE_RADIO_RELEASE_NORMAL, "CODE_RADIO_RELEASE_NORMAL");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_RADIO_RELEASE_ABNORMAL, "CODE_RADIO_RELEASE_ABNORMAL");
+            sImsCodeMap.put(ImsReasonInfo.CODE_ACCESS_CLASS_BLOCKED, "CODE_ACCESS_CLASS_BLOCKED");
+            sImsCodeMap.put(ImsReasonInfo.CODE_NETWORK_DETACH, "CODE_NETWORK_DETACH");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL,
+                    "CODE_SIP_ALTERNATE_EMERGENCY_CALL");
+            sImsCodeMap.put(ImsReasonInfo.CODE_UNOBTAINABLE_NUMBER, "CODE_UNOBTAINABLE_NUMBER");
+            sImsCodeMap.put(ImsReasonInfo.CODE_NO_CSFB_IN_CS_ROAM, "CODE_NO_CSFB_IN_CS_ROAM");
+            sImsCodeMap.put(ImsReasonInfo.CODE_REJECT_UNKNOWN, "CODE_REJECT_UNKNOWN");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_ONGOING_CALL_WAITING_DISABLED,
+                    "CODE_REJECT_ONGOING_CALL_WAITING_DISABLED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_CALL_ON_OTHER_SUB, "CODE_REJECT_CALL_ON_OTHER_SUB");
+            sImsCodeMap.put(ImsReasonInfo.CODE_REJECT_1X_COLLISION, "CODE_REJECT_1X_COLLISION");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_SERVICE_NOT_REGISTERED,
+                    "CODE_REJECT_SERVICE_NOT_REGISTERED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_CALL_TYPE_NOT_ALLOWED,
+                    "CODE_REJECT_CALL_TYPE_NOT_ALLOWED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_ONGOING_E911_CALL, "CODE_REJECT_ONGOING_E911_CALL");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_ONGOING_CALL_SETUP, "CODE_REJECT_ONGOING_CALL_SETUP");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_MAX_CALL_LIMIT_REACHED,
+                    "CODE_REJECT_MAX_CALL_LIMIT_REACHED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_UNSUPPORTED_SIP_HEADERS,
+                    "CODE_REJECT_UNSUPPORTED_SIP_HEADERS");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_UNSUPPORTED_SDP_HEADERS,
+                    "CODE_REJECT_UNSUPPORTED_SDP_HEADERS");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_ONGOING_CALL_TRANSFER,
+                    "CODE_REJECT_ONGOING_CALL_TRANSFER");
+            sImsCodeMap.put(ImsReasonInfo.CODE_REJECT_INTERNAL_ERROR, "CODE_REJECT_INTERNAL_ERROR");
+            sImsCodeMap.put(ImsReasonInfo.CODE_REJECT_QOS_FAILURE, "CODE_REJECT_QOS_FAILURE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_ONGOING_HANDOVER, "CODE_REJECT_ONGOING_HANDOVER");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_VT_TTY_NOT_ALLOWED, "CODE_REJECT_VT_TTY_NOT_ALLOWED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_ONGOING_CALL_UPGRADE,
+                    "CODE_REJECT_ONGOING_CALL_UPGRADE");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_CONFERENCE_TTY_NOT_ALLOWED,
+                    "CODE_REJECT_CONFERENCE_TTY_NOT_ALLOWED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_ONGOING_CONFERENCE_CALL,
+                    "CODE_REJECT_ONGOING_CONFERENCE_CALL");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_VT_AVPF_NOT_ALLOWED,
+                    "CODE_REJECT_VT_AVPF_NOT_ALLOWED");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_ONGOING_ENCRYPTED_CALL,
+                    "CODE_REJECT_ONGOING_ENCRYPTED_CALL");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_REJECT_ONGOING_CS_CALL, "CODE_REJECT_ONGOING_CS_CALL");
+            sImsCodeMap.put(ImsReasonInfo.CODE_NETWORK_CONGESTION, "CODE_NETWORK_CONGESTION");
+            sImsCodeMap.put(
+                    ImsReasonInfo.CODE_RETRY_ON_IMS_WITHOUT_RTT, "CODE_RETRY_ON_IMS_WITHOUT_RTT");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_1, "CODE_OEM_CAUSE_1");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_2, "CODE_OEM_CAUSE_2");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_3, "CODE_OEM_CAUSE_3");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_4, "CODE_OEM_CAUSE_4");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_5, "CODE_OEM_CAUSE_5");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_6, "CODE_OEM_CAUSE_6");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_7, "CODE_OEM_CAUSE_7");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_8, "CODE_OEM_CAUSE_8");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_9, "CODE_OEM_CAUSE_9");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_10, "CODE_OEM_CAUSE_10");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_11, "CODE_OEM_CAUSE_11");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_12, "CODE_OEM_CAUSE_12");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_13, "CODE_OEM_CAUSE_13");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_14, "CODE_OEM_CAUSE_14");
+            sImsCodeMap.put(ImsReasonInfo.CODE_OEM_CAUSE_15, "CODE_OEM_CAUSE_15");
+        }
+    }
+
+    /**
+     * Implements and Registers the required Listeners and BroadcastReceivers for receiving
+     * ServiceState related information. Performs required logic on received data and then Passes
+     * the information to its provider class for further processing.
+     */
+    public class ServiceStateAnalytics extends TelephonyCallback
+            implements TelephonyCallback.ServiceStateListener {
+        private final Executor mExecutor;
+        private static final String TAG = ServiceStateAnalytics.class.getSimpleName();
+        private static final int BUFFER_TIME = 10000;
+
+        private TelephonyManager mTelephonyManager;
+
+        private enum DeviceStatus {
+            APM,
+            CELLULAR_OOS_WITH_IWLAN,
+            NO_NETWORK_COVERAGE,
+            SIM_ABSENT,
+            IN_SERVICE;
+        }
+
+        private final AtomicReference<TimeStampedServiceState> mLastState =
+                new AtomicReference<>(null);
+        private static final String NA = "NA";
+        private final BroadcastReceiver mBroadcastReceiver =
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        final long now = getTimeMillis();
+                        if (intent.getAction()
+                                .equals(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)) {
+                            int simState =
+                                    intent.getIntExtra(
+                                            TelephonyManager.EXTRA_SIM_STATE,
+                                            TelephonyManager.SIM_STATE_UNKNOWN);
+                            if (simState == TelephonyManager.SIM_STATE_ABSENT) {
+                                Rlog.d("AnkitSimAbsent", "Sim is Absent");
+                                logSimAbsentState();
+                            }
+                        }
+                    }
+                };
+
+        protected ServiceStateAnalytics(Executor executor) {
+            super();
+            mExecutor = executor;
+            IntentFilter mIntentFilter =
+                    new IntentFilter(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
+            mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
+        }
+
+        @Override
+        public void onServiceStateChanged(@NonNull ServiceState serviceState) {
+            int dataRegState = serviceState.getDataRegState();
+            int voiceRegState = serviceState.getVoiceRegState();
+            int voiceRadioTechnology = serviceState.getRilVoiceRadioTechnology();
+            int dataRadioTechnology = serviceState.getRilDataRadioTechnology();
+
+            mExecutorService.execute(() -> {
+                logServiceState(dataRegState, voiceRegState, voiceRadioTechnology,
+                        dataRadioTechnology);
+            });
+        }
+
+        private void logServiceState(
+                int dataRegState,
+                int voiceRegState,
+                int voiceRadioTechnology,
+                int dataRadioTechnology) {
+            long now = getTimeMillis();
+            String voiceRadioTechnologyName =
+                    ServiceState.rilRadioTechnologyToString(voiceRadioTechnology);
+            String dataRadioTechnologyName =
+                    ServiceState.rilRadioTechnologyToString(dataRadioTechnology);
+
+            if (isAirplaneModeOn()) {
+                if (dataRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+                        && dataRegState == ServiceState.STATE_IN_SERVICE) {
+                    logOosWithIwlan(now);
+                } else {
+                    logAirplaneModeServiceState(now);
+                }
+            } else {
+                if (voiceRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
+                        && dataRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
+                    logNoNetworkCoverage(now);
+
+                } else if (voiceRadioTechnology != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
+                        && dataRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
+                    if (voiceRegState == ServiceState.STATE_IN_SERVICE) {
+                        logInServiceData(voiceRadioTechnologyName, now);
+                    } else {
+                        logNoNetworkCoverage(now);
+                    }
+                } else if (voiceRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
+                    if (dataRegState == ServiceState.STATE_IN_SERVICE) {
+                        if (dataRadioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
+                            logOosWithIwlan(now);
+                        } else {
+                            logInServiceData(dataRadioTechnologyName, now);
+                        }
+                    } else {
+                        logNoNetworkCoverage(now);
+                    }
+                } else {
+                    if (dataRegState == ServiceState.STATE_IN_SERVICE
+                            || voiceRegState == ServiceState.STATE_IN_SERVICE) {
+                        logInServiceData(voiceRadioTechnologyName, now);
+                    } else {
+                        logNoNetworkCoverage(now);
+                    }
+                }
+            }
+        }
+
+        private void logSimAbsentState() {
+            long now = getTimeMillis();
+            TimeStampedServiceState currentState =
+                    new TimeStampedServiceState(
+                            mSlotIndex, NA, DeviceStatus.SIM_ABSENT.name(), now);
+            setCurrentStateAndAddLastState(currentState, now);
+        }
+        private void logOosWithIwlan(long now) {
+            TimeStampedServiceState currentState =
+                    new TimeStampedServiceState(mSlotIndex, NA,
+                            DeviceStatus.CELLULAR_OOS_WITH_IWLAN.name(), now);
+            setCurrentStateAndAddLastState(currentState, now);
+        }
+
+        private void logAirplaneModeServiceState(long now) {
+            TimeStampedServiceState currentState =
+                    new TimeStampedServiceState(mSlotIndex, NA, DeviceStatus.APM.name(), now);
+            setCurrentStateAndAddLastState(currentState, now);
+        }
+
+        private void logNoNetworkCoverage(long now) {
+            TimeStampedServiceState currentState =
+                    new TimeStampedServiceState(
+                            mSlotIndex, NA, DeviceStatus.NO_NETWORK_COVERAGE.name(), now);
+            setCurrentStateAndAddLastState(currentState, now);
+        }
+
+        private void logInServiceData(String rat, long now) {
+            TimeStampedServiceState currentState =
+                    new TimeStampedServiceState(
+                            mSlotIndex, rat, DeviceStatus.IN_SERVICE.name(), now);
+            setCurrentStateAndAddLastState(currentState, now);
+        }
+
+        private void setCurrentStateAndAddLastState(
+                TimeStampedServiceState currentState, long now) {
+            TimeStampedServiceState lastState = mLastState.getAndSet(currentState);
+            addData(lastState, now);
+        }
+
+        private void addData(TimeStampedServiceState lastState, long now) {
+            if (lastState == null) {
+                return;
+            }
+            if (now - lastState.mTimestampStart < BUFFER_TIME) {
+                return;
+            }
+            Rlog.d(TAG, "Last State = " + lastState.toString() + "End = " + now);
+            mServiceStateAnalyticsProvider.insertDataToDb(lastState, now);
+        }
+
+        private void recordCurrentStateBeforeDump() {
+            long now = getTimeMillis();
+            Rlog.d("RecordingStateBDump", "Recording " + now);
+            TimeStampedServiceState currentState = mLastState.get();
+            mLastState.set(createCopyWithUpdatedTimestamp(currentState));
+            addData(currentState, now);
+        }
+
+        private TimeStampedServiceState createCopyWithUpdatedTimestamp(
+                TimeStampedServiceState currentState) {
+            if (currentState == null) {
+                return null;
+            }
+            long now = getTimeMillis();
+            TimeStampedServiceState state =
+                    new TimeStampedServiceState(
+                            currentState.mSlotIndex,
+                            currentState.mRAT,
+                            currentState.mDeviceStatus,
+                            now);
+            return state;
+        }
+
+        private boolean isAirplaneModeOn() {
+            return Settings.Global.getInt(
+                    mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0)
+                    != 0;
+        }
+
+        protected long getTimeMillis() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        void registerMyListener(Context context, int subId) {
+            try {
+                mTelephonyManager =
+                        context.getSystemService(TelephonyManager.class)
+                                .createForSubscriptionId(subId);
+                mTelephonyManager.registerTelephonyCallback(mExecutor, this);
+
+            } catch (NullPointerException e) {
+                log("Null pointer exception caught " + e);
+            }
+        }
+
+        void unregisterMyListener(int subId) {
+            mTelephonyManager.unregisterTelephonyCallback(this);
+        }
+
+        private void log(String s) {
+            Rlog.d(ServiceStateAnalytics.class.getSimpleName(), s);
+        }
+
+        /**
+         * Serves the functionality of storing service state related information,
+         * Along with the timestamp at which the state was detected.
+         */
+        public static class TimeStampedServiceState {
+            protected final int mSlotIndex;
+            protected final String mRAT;
+            protected final String mDeviceStatus;
+            protected final long mTimestampStart;
+
+            public TimeStampedServiceState(
+                    int slotIndex, String rat, String deviceStatus, long timestampStart) {
+                mSlotIndex = slotIndex;
+                mRAT = rat;
+                mDeviceStatus = deviceStatus;
+                mTimestampStart = timestampStart;
+            }
+
+            @Override
+            public String toString() {
+                return "SlotIndex = "
+                        + mSlotIndex
+                        + " RAT = "
+                        + mRAT
+                        + " DeviceStatus = "
+                        + mDeviceStatus
+                        + "TimeStampStart = "
+                        + mTimestampStart;
+            }
+            /** Getter function for slotIndex */
+            public int getSlotIndex() {
+                return mSlotIndex;
+            }
+
+            /** Getter function for state start Timestamp */
+            public long getTimestampStart() {
+                return mTimestampStart;
+            }
+
+            /** Getter function for device Status */
+            public String getDeviceStatus() {
+                return mDeviceStatus;
+            }
+
+            /** Getter function for radio access technology  */
+            public String getRAT() {
+                return mRAT;
+            }
+        }
+    }
+
+    /**
+     * Provides implementation for processing received Sms related data. Implements functions to
+     * handle various scenarios pertaining to Sms. Passes the data to its provider for further
+     * processing.
+     */
+    public class SmsMmsAnalytics {
+        private static final String TAG = SmsMmsAnalytics.class.getSimpleName();
+        public SmsMmsAnalytics() {
+
+        }
+
+        /** Collects Outgoing Sms related information. */
+        public void onOutgoingSms(boolean isOverIms, @SmsManager.Result int sendErrorCode) {
+            Rlog.d(
+                    TAG,
+                    "Is Over Ims = "
+                            + isOverIms
+                            + " sendErrorCode = "
+                            + sendErrorCode
+                            + "SlotInfo ="
+                            + mSlotIndex);
+            logOutgoingSms(isOverIms, sendErrorCode);
+        }
+
+        /** Collects Successful Incoming Sms related information. */
+        public void onIncomingSmsSuccess(@InboundSmsHandler.SmsSource int smsSource) {
+            Rlog.d(TAG, " smsSource = " + smsSource);
+            String status = "Success";
+            String failureReason = "NA";
+            logIncomingSms(smsSource, status, failureReason);
+        }
+
+        /** Collects Failed Incoming Multipart Sms related information. */
+        public void onDroppedIncomingMultipartSms() {
+            String status = "Failure";
+            String type = "SMS Incoming";
+            // Mark the RAT as unknown since it might have changed over time.
+            int rat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+            String ratString = ServiceState.rilRadioTechnologyToString(rat);
+            String failureReason = "INCOMING_SMS__ERROR__SMS_ERROR_GENERIC";
+            sendDataToProvider(status, type, ratString, failureReason);
+        }
+
+        /** Collects Failed Incoming Sms related information. */
+        public void onIncomingSmsError(@InboundSmsHandler.SmsSource int smsSource, int result) {
+            String status = "Failure";
+            String failureReason = getIncomingSmsErrorString(result);
+            logIncomingSms(smsSource, status, failureReason);
+            Rlog.d(
+                    TAG,
+                    " smsSource = "
+                            + smsSource
+                            + "Result = "
+                            + result
+                            + "IncomingError = "
+                            + failureReason
+                            + "("
+                            + getIncomingError(result)
+                            + ")");
+        }
+
+        private void logOutgoingSms(boolean isOverIms, @SmsManager.Result int sendErrorCode) {
+            try {
+                String type = "SMS Outgoing";
+                String status = sendErrorCode == 0 ? "Success" : "Failure";
+                int rat = getRat(isOverIms);
+                String ratString = TelephonyManager.getNetworkTypeName(rat);
+                String failureReason =
+                        status.equals("Success") ? "NA" : getSmsFailureReasonString(sendErrorCode);
+                Rlog.d(
+                        TAG,
+                        "SlotInfo = "
+                                + mSlotIndex
+                                + " Type = "
+                                + type
+                                + " Status = "
+                                + status
+                                + "RAT "
+                                + ratString
+                                + " "
+                                + rat
+                                + "Failure Reason = "
+                                + failureReason);
+                sendDataToProvider(status, type, ratString, failureReason);
+
+            } catch (Exception e) {
+                Rlog.d(TAG, "Error in SmsLogs" + e);
+            }
+        }
+
+        private void logIncomingSms(
+                @InboundSmsHandler.SmsSource int smsSource, String status, String failureReason) {
+            String type = "SMS Incoming";
+            try {
+                int rat = getRat(smsSource);
+                String ratString = TelephonyManager.getNetworkTypeName(rat);
+                sendDataToProvider(status, type, ratString, failureReason);
+                Rlog.d(
+                        TAG,
+                        "SlotInfo ="
+                                + mSlotIndex
+                                + " Type = "
+                                + type
+                                + " Status = "
+                                + status
+                                + " RAT "
+                                + ratString
+                                + " ("
+                                + rat
+                                + " ) Failure Reason = "
+                                + failureReason);
+            } catch (Exception e) {
+                Rlog.e(TAG, "Exception = " + e);
+            }
+        }
+
+        private void sendDataToProvider(
+                String status, String type, String rat, String failureReason) {
+            mExecutorService.execute(() -> {
+                mSmsMmsAnalyticsProvider.insertDataToDb(status, type, rat, failureReason);
+            });
+        }
+
+        private static int getIncomingError(int result) {
+            switch (result) {
+                case Activity.RESULT_OK:
+                case Intents.RESULT_SMS_HANDLED:
+                    return INCOMING_SMS__ERROR__SMS_SUCCESS;
+                case Intents.RESULT_SMS_OUT_OF_MEMORY:
+                    return INCOMING_SMS__ERROR__SMS_ERROR_NO_MEMORY;
+                case Intents.RESULT_SMS_UNSUPPORTED:
+                    return INCOMING_SMS__ERROR__SMS_ERROR_NOT_SUPPORTED;
+                case Intents.RESULT_SMS_GENERIC_ERROR:
+                default:
+                    return INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
+            }
+        }
+
+        private static String getIncomingSmsErrorString(int result) {
+            switch (result) {
+                case Activity.RESULT_OK:
+                case Intents.RESULT_SMS_HANDLED:
+                    return "INCOMING_SMS__ERROR__SMS_SUCCESS";
+                case Intents.RESULT_SMS_OUT_OF_MEMORY:
+                    return "INCOMING_SMS__ERROR__SMS_ERROR_NO_MEMORY";
+                case Intents.RESULT_SMS_UNSUPPORTED:
+                    return "INCOMING_SMS__ERROR__SMS_ERROR_NOT_SUPPORTED";
+                case Intents.RESULT_SMS_GENERIC_ERROR:
+                default:
+                    return "INCOMING_SMS__ERROR__SMS_ERROR_GENERIC";
+            }
+        }
+
+        @Nullable
+        private ServiceState getServiceState() {
+            Phone phone = mPhone;
+            if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
+                phone = mPhone.getDefaultPhone();
+            }
+            ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker();
+            return serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
+        }
+
+        @Annotation.NetworkType
+        private int getRat(@InboundSmsHandler.SmsSource int smsSource) {
+            if (smsSource == SOURCE_INJECTED_FROM_UNKNOWN) {
+                return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+            }
+            return getRat(smsSource == SOURCE_INJECTED_FROM_IMS);
+        }
+
+        @Annotation.NetworkType
+        private int getRat(boolean isOverIms) {
+            if (isOverIms) {
+                if (mPhone.getImsRegistrationTech()
+                        == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN) {
+                    return TelephonyManager.NETWORK_TYPE_IWLAN;
+                }
+            }
+            ServiceState serviceState = getServiceState();
+            return serviceState != null
+                    ? serviceState.getVoiceNetworkType()
+                    : TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        }
+
+        private String getSmsFailureReasonString(int sendErrorCode) {
+            switch (sendErrorCode) {
+                case SmsManager.RESULT_ERROR_NONE:
+                    return "RESULT_ERROR_NONE";
+                case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
+                    return "RESULT_ERROR_GENERIC_FAILURE";
+                case SmsManager.RESULT_ERROR_RADIO_OFF:
+                    return "RESULT_ERROR_RADIO_OFF";
+                case SmsManager.RESULT_ERROR_NULL_PDU:
+                    return "RESULT_ERROR_NULL_PDU";
+                case SmsManager.RESULT_ERROR_NO_SERVICE:
+                    return "RESULT_ERROR_NO_SERVICE";
+                case SmsManager.RESULT_ERROR_LIMIT_EXCEEDED:
+                    return "RESULT_ERROR_LIMIT_EXCEEDED";
+                case SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE:
+                    return "RESULT_ERROR_FDN_CHECK_FAILURE";
+                case SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED:
+                    return "RESULT_ERROR_SHORT_CODE_NOT_ALLOWED";
+                case SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED:
+                    return "RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED";
+                case SmsManager.RESULT_RADIO_NOT_AVAILABLE:
+                    return "RESULT_RADIO_NOT_AVAILABLE";
+                case SmsManager.RESULT_NETWORK_REJECT:
+                    return "RESULT_NETWORK_REJECT";
+                case SmsManager.RESULT_INVALID_ARGUMENTS:
+                    return "RESULT_INVALID_ARGUMENTS";
+                case SmsManager.RESULT_INVALID_STATE:
+                    return "RESULT_INVALID_STATE";
+                case SmsManager.RESULT_NO_MEMORY:
+                    return "RESULT_NO_MEMORY";
+                case SmsManager.RESULT_INVALID_SMS_FORMAT:
+                    return "RESULT_INVALID_SMS_FORMAT";
+                case SmsManager.RESULT_SYSTEM_ERROR:
+                    return "RESULT_SYSTEM_ERROR";
+                case SmsManager.RESULT_MODEM_ERROR:
+                    return "RESULT_MODEM_ERROR";
+                case SmsManager.RESULT_NETWORK_ERROR:
+                    return "RESULT_NETWORK_ERROR";
+                case SmsManager.RESULT_INVALID_SMSC_ADDRESS:
+                    return "RESULT_INVALID_SMSC_ADDRESS";
+                case SmsManager.RESULT_OPERATION_NOT_ALLOWED:
+                    return "RESULT_OPERATION_NOT_ALLOWED";
+                case SmsManager.RESULT_INTERNAL_ERROR:
+                    return "RESULT_INTERNAL_ERROR";
+                case SmsManager.RESULT_NO_RESOURCES:
+                    return "RESULT_NO_RESOURCES";
+                case SmsManager.RESULT_CANCELLED:
+                    return "RESULT_CANCELLED";
+                case SmsManager.RESULT_REQUEST_NOT_SUPPORTED:
+                    return "RESULT_REQUEST_NOT_SUPPORTED";
+                case SmsManager.RESULT_NO_BLUETOOTH_SERVICE:
+                    return "RESULT_NO_BLUETOOTH_SERVICE";
+                case SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS:
+                    return "RESULT_INVALID_BLUETOOTH_ADDRESS";
+                case SmsManager.RESULT_BLUETOOTH_DISCONNECTED:
+                    return "RESULT_BLUETOOTH_DISCONNECTED";
+                case SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING:
+                    return "RESULT_UNEXPECTED_EVENT_STOP_SENDING";
+                case SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY:
+                    return "RESULT_SMS_BLOCKED_DURING_EMERGENCY";
+                case SmsManager.RESULT_SMS_SEND_RETRY_FAILED:
+                    return "RESULT_SMS_SEND_RETRY_FAILED";
+                case SmsManager.RESULT_REMOTE_EXCEPTION:
+                    return "RESULT_REMOTE_EXCEPTION";
+                case SmsManager.RESULT_NO_DEFAULT_SMS_APP:
+                    return "RESULT_NO_DEFAULT_SMS_APP";
+                case SmsManager.RESULT_USER_NOT_ALLOWED:
+                    return "RESULT_USER_NOT_ALLOWED";
+                case SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE:
+                    return "RESULT_RIL_RADIO_NOT_AVAILABLE";
+                case SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY:
+                    return "RESULT_RIL_SMS_SEND_FAIL_RETRY";
+                case SmsManager.RESULT_RIL_NETWORK_REJECT:
+                    return "RESULT_RIL_NETWORK_REJECT";
+                case SmsManager.RESULT_RIL_INVALID_STATE:
+                    return "RESULT_RIL_INVALID_STATE";
+                case SmsManager.RESULT_RIL_INVALID_ARGUMENTS:
+                    return "RESULT_RIL_INVALID_ARGUMENTS";
+                case SmsManager.RESULT_RIL_NO_MEMORY:
+                    return "RESULT_RIL_NO_MEMORY";
+                case SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED:
+                    return "RESULT_RIL_REQUEST_RATE_LIMITED";
+                case SmsManager.RESULT_RIL_INVALID_SMS_FORMAT:
+                    return "RESULT_RIL_INVALID_SMS_FORMAT";
+                case SmsManager.RESULT_RIL_SYSTEM_ERR:
+                    return "RESULT_RIL_SYSTEM_ERR";
+                case SmsManager.RESULT_RIL_ENCODING_ERR:
+                    return "RESULT_RIL_ENCODING_ERR";
+                case SmsManager.RESULT_RIL_INVALID_SMSC_ADDRESS:
+                    return "RESULT_RIL_INVALID_SMSC_ADDRESS";
+                case SmsManager.RESULT_RIL_MODEM_ERR:
+                    return "RESULT_RIL_MODEM_ERR";
+                case SmsManager.RESULT_RIL_NETWORK_ERR:
+                    return "RESULT_RIL_NETWORK_ERR";
+                case SmsManager.RESULT_RIL_INTERNAL_ERR:
+                    return "RESULT_RIL_INTERNAL_ERR";
+                case SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED:
+                    return "RESULT_RIL_REQUEST_NOT_SUPPORTED";
+                case SmsManager.RESULT_RIL_INVALID_MODEM_STATE:
+                    return "RESULT_RIL_INVALID_MODEM_STATE";
+                case SmsManager.RESULT_RIL_NETWORK_NOT_READY:
+                    return "RESULT_RIL_NETWORK_NOT_READY";
+                case SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED:
+                    return "RESULT_RIL_OPERATION_NOT_ALLOWED";
+                case SmsManager.RESULT_RIL_NO_RESOURCES:
+                    return "RESULT_RIL_NO_RESOURCES";
+                case SmsManager.RESULT_RIL_CANCELLED:
+                    return "RESULT_RIL_CANCELLED";
+                case SmsManager.RESULT_RIL_SIM_ABSENT:
+                    return "RESULT_RIL_SIM_ABSENT";
+                case SmsManager.RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED:
+                    return "RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED";
+                case SmsManager.RESULT_RIL_ACCESS_BARRED:
+                    return "RESULT_RIL_ACCESS_BARRED";
+                case SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL:
+                    return "RESULT_RIL_BLOCKED_DUE_TO_CALL";
+                case SmsManager.RESULT_RIL_GENERIC_ERROR:
+                    return "RESULT_RIL_GENERIC_ERROR";
+                case SmsManager.RESULT_RIL_INVALID_RESPONSE:
+                    return "RESULT_RIL_INVALID_RESPONSE";
+                case SmsManager.RESULT_RIL_SIM_PIN2:
+                    return "RESULT_RIL_SIM_PIN2";
+                case SmsManager.RESULT_RIL_SIM_PUK2:
+                    return "RESULT_RIL_SIM_PUK2";
+                case SmsManager.RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE:
+                    return "RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE";
+                case SmsManager.RESULT_RIL_SIM_ERROR:
+                    return "RESULT_RIL_SIM_ERROR";
+                case SmsManager.RESULT_RIL_INVALID_SIM_STATE:
+                    return "RESULT_RIL_INVALID_SIM_STATE";
+                case SmsManager.RESULT_RIL_NO_SMS_TO_ACK:
+                    return "RESULT_RIL_NO_SMS_TO_ACK";
+                case SmsManager.RESULT_RIL_SIM_BUSY:
+                    return "RESULT_RIL_SIM_BUSY";
+                case SmsManager.RESULT_RIL_SIM_FULL:
+                    return "RESULT_RIL_SIM_FULL";
+                case SmsManager.RESULT_RIL_NO_SUBSCRIPTION:
+                    return "RESULT_RIL_NO_SUBSCRIPTION";
+                case SmsManager.RESULT_RIL_NO_NETWORK_FOUND:
+                    return "RESULT_RIL_NO_NETWORK_FOUND";
+                case SmsManager.RESULT_RIL_DEVICE_IN_USE:
+                    return "RESULT_RIL_DEVICE_IN_USE";
+                case SmsManager.RESULT_RIL_ABORTED:
+                    return "RESULT_RIL_ABORTED";
+                default:
+                    return "NA";
+            }
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/analytics/TelephonyAnalyticsDatabase.java b/src/java/com/android/internal/telephony/analytics/TelephonyAnalyticsDatabase.java
new file mode 100644
index 0000000..673eef9
--- /dev/null
+++ b/src/java/com/android/internal/telephony/analytics/TelephonyAnalyticsDatabase.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import android.provider.BaseColumns;
+
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Defines the tables classes which are present in the Telephony Analytics Database, private
+ * constructor to prevent instantiation.
+ */
+public final class TelephonyAnalyticsDatabase {
+    private TelephonyAnalyticsDatabase() {}
+
+    public static final DateTimeFormatter DATE_FORMAT =
+            DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault());
+
+    /**
+     * CallAnalyticsTable class defines the columns in the CallTable Implements the BaseColumns
+     * class.
+     */
+    public static final class CallAnalyticsTable implements BaseColumns {
+        public static final String TABLE_NAME = "CallDataLogs";
+        public static final String LOG_DATE = "LogDate";
+        public static final String CALL_STATUS = "CallStatus";
+        public static final String CALL_TYPE = "CallType";
+        public static final String RAT = "RAT";
+        public static final String SLOT_ID = "SlotID";
+        public static final String FAILURE_REASON = "FailureReason";
+        public static final String RELEASE_VERSION = "ReleaseVersion";
+        public static final String COUNT = "Count";
+    }
+
+    /**
+     * SmsMmsAnalyticsTable class defines the columns in the SmsMmsTable Implements the BaseColumns
+     * class.
+     */
+    public static final class SmsMmsAnalyticsTable implements BaseColumns {
+        public static final String TABLE_NAME = "SmsMmsDataLogs";
+        public static final String LOG_DATE = "LogDate";
+        public static final String SMS_MMS_STATUS = "SmsMmsStatus";
+        public static final String SMS_MMS_TYPE = "SmsMmsType";
+        public static final String SLOT_ID = "SlotID";
+        public static final String RAT = "RAT";
+        public static final String FAILURE_REASON = "FailureReason";
+        public static final String RELEASE_VERSION = "ReleaseVersion";
+        public static final String COUNT = "Count";
+    }
+
+    /**
+     * ServiceStateAnalyticsTable class defines the columns in the ServiceStateTable Implements the
+     * BaseColumns class.
+     */
+    public static final class ServiceStateAnalyticsTable implements BaseColumns {
+        public static final String TABLE_NAME = "ServiceStateLogs";
+        public static final String LOG_DATE = "LogDate";
+        public static final String TIME_DURATION = "TimeDuration";
+        public static final String SLOT_ID = "SlotID";
+        public static final String RAT = "RAT";
+        public static final String DEVICE_STATUS = "DeviceStatus";
+        public static final String RELEASE_VERSION = "ReleaseVersion";
+    }
+}
diff --git a/src/java/com/android/internal/telephony/analytics/TelephonyAnalyticsProvider.java b/src/java/com/android/internal/telephony/analytics/TelephonyAnalyticsProvider.java
new file mode 100644
index 0000000..32443cd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/analytics/TelephonyAnalyticsProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import java.util.ArrayList;
+/**
+ * Interface for Telephony Provider Classes
+ */
+public interface TelephonyAnalyticsProvider {
+    /**
+     * Aggregates all information from the db.
+     * Used when the bugreport is to be pulled.
+     * @return All the aggregated information in a ArrayList.
+     */
+    ArrayList<String> aggregate();
+}
diff --git a/src/java/com/android/internal/telephony/analytics/TelephonyAnalyticsUtil.java b/src/java/com/android/internal/telephony/analytics/TelephonyAnalyticsUtil.java
new file mode 100644
index 0000000..78b607b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/analytics/TelephonyAnalyticsUtil.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import static com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.DATE_FORMAT;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
+
+import java.util.Calendar;
+
+/**
+ * Singleton Utility class to support TelephonyAnalytics Extends SQLiteOpenHelper class. Supports db
+ * related operations which includes creating tables,insertion,updation,deletion. Supports some
+ * generic functionality like getting the Cursor resulting from a query.
+ */
+public class TelephonyAnalyticsUtil extends SQLiteOpenHelper {
+    private static TelephonyAnalyticsUtil sTelephonyAnalyticsUtil;
+    private static final String DATABASE_NAME = "telephony_analytics.db";
+    private static final int DATABASE_VERSION = 10;
+    private static final String TAG = TelephonyAnalyticsUtil.class.getSimpleName();
+    private static final int MAX_ENTRIES_LIMIT = 1000;
+    private static final int CUTOFF_MONTHS = 2;
+
+    private TelephonyAnalyticsUtil(Context context) {
+        super(context, DATABASE_NAME, null, DATABASE_VERSION);
+    }
+
+    /**
+     * Get the instance of the TelephonyAnalyticsUtil class. Instantiates the TelephonyAnalyticsUtil
+     * object sTelephonyAnalyticsUtil only once.
+     *
+     * @return Returns the TelephonyAnalyticsUtil object sTelephonyAnalyticsUtil.
+     */
+    public static synchronized TelephonyAnalyticsUtil getInstance(Context context) {
+        if (sTelephonyAnalyticsUtil == null) {
+            sTelephonyAnalyticsUtil = new TelephonyAnalyticsUtil(context);
+        }
+        return sTelephonyAnalyticsUtil;
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {}
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
+
+    /**
+     * Uses Util class functionality to create CallTable in db
+     *
+     * @param createTableQuery : CallTable Schema
+     */
+    @VisibleForTesting
+    public synchronized void createTable(String createTableQuery) {
+        try {
+            SQLiteDatabase db = getWritableDatabase();
+            db.execSQL(createTableQuery);
+        } catch (Exception e) {
+            Rlog.e(TAG, "Error during table creation : " + e);
+        }
+    }
+
+    /** Utility function that performs insertion on the given database table */
+    @VisibleForTesting
+    public synchronized void insert(String tableName, ContentValues values) {
+        try {
+            SQLiteDatabase db = getWritableDatabase();
+            db.insert(tableName, null, values);
+        } catch (SQLException e) {
+            Rlog.e(TAG, "error occurred during insertion");
+        }
+    }
+
+    /** Utility function that performs update query on the given database table */
+    @VisibleForTesting
+    public synchronized int update(
+            String table, ContentValues values, String whereClause, String[] whereArgs) {
+        int rowsAffected = -1;
+        try {
+            SQLiteDatabase db = getWritableDatabase();
+            rowsAffected = db.update(table, values, whereClause, whereArgs);
+
+        } catch (SQLException e) {
+            Rlog.e(TAG, "Error during update.");
+        }
+        return rowsAffected;
+    }
+
+    /**
+     * @Return the cursor object obtained from running a query based on given parameters.
+     */
+    @VisibleForTesting
+    public synchronized Cursor getCursor(
+            String tableName,
+            String[] columns,
+            String selection,
+            String[] selectionArgs,
+            String groupBy,
+            String having,
+            String orderBy,
+            String limit) {
+
+        Cursor cursor = null;
+        try {
+            SQLiteDatabase db = getReadableDatabase();
+            cursor =
+                    db.query(
+                            tableName,
+                            columns,
+                            selection,
+                            selectionArgs,
+                            groupBy,
+                            having,
+                            orderBy,
+                            limit);
+        } catch (SQLException e) {
+            Rlog.e(TAG, "Error during querying for getCursor()" + e);
+        }
+        return cursor;
+    }
+
+    /** Returns the count stored in the cursor obtained from query execution. */
+    @VisibleForTesting
+    public synchronized long getCountFromCursor(Cursor cursor) {
+        long count = 0;
+        if (cursor != null && cursor.moveToFirst()) {
+            count = cursor.getInt(0);
+        }
+        return count;
+    }
+
+    /** Deletes Old Data and Overflow data in the db. */
+    @VisibleForTesting
+    public void deleteOverflowAndOldData(
+            String tableName, String overflowWhereClause, String oldDataWhereClause) {
+        deleteOverFlowData(tableName, overflowWhereClause);
+        deleteOldData(tableName, oldDataWhereClause);
+    }
+
+    protected void deleteOverFlowData(String tableName, String whereClause) {
+        String[] whereArgs = {Integer.toString(MAX_ENTRIES_LIMIT)};
+        delete(tableName, whereClause, whereArgs);
+    }
+
+    protected void deleteOldData(String tableName, String whereClause) {
+        String[] whereArgs = {getCutoffDate()};
+        delete(tableName, whereClause, whereArgs);
+    }
+
+    /** Utility function that performs deletion on the database table */
+    @VisibleForTesting
+    public synchronized void delete(String tableName, String whereClause, String[] whereArgs) {
+        try {
+            SQLiteDatabase db = getWritableDatabase();
+            db.delete(tableName, whereClause, whereArgs);
+        } catch (SQLException e) {
+            Rlog.e(TAG, "Sqlite Operation Error during deletion of Overflow data " + e);
+        }
+    }
+
+    private String getCutoffDate() {
+        Calendar cutoffDate = Calendar.getInstance();
+        cutoffDate.add(Calendar.MONTH, -1 * CUTOFF_MONTHS);
+        return DATE_FORMAT.format(cutoffDate.toInstant());
+    }
+}
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index fa2b19b..cadb02e 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -49,6 +49,8 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.ProxyController;
 import com.android.internal.telephony.SmsController;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.IccFileHandler;
@@ -104,6 +106,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private static CatService[] sInstance = null;
     @UnsupportedAppUsage
+    private static final FeatureFlags sFlags = new FeatureFlagsImpl();
+    @UnsupportedAppUsage
     private CommandsInterface mCmdIf;
     @UnsupportedAppUsage
     private Context mContext;
@@ -487,7 +491,8 @@
                         destAddr = ((SendSMSParams) cmdParams).mDestAddress.text;
                     }
                     if (text != null && destAddr != null) {
-                        ProxyController proxyController = ProxyController.getInstance(mContext);
+                        ProxyController proxyController = ProxyController
+                                .getInstance(mContext, sFlags);
                         SubscriptionManager subscriptionManager = (SubscriptionManager)
                                 mContext.getSystemService(
                                         Context.TELEPHONY_SUBSCRIPTION_SERVICE);
diff --git a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
index 65f3c4a..cb96c67 100644
--- a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -29,6 +29,7 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.telephony.AnomalyReporter;
 import android.telephony.SmsMessage;
 import android.text.TextUtils;
 
@@ -37,6 +38,7 @@
 
 import java.util.Iterator;
 import java.util.List;
+import java.util.UUID;
 import java.util.Locale;
 /**
  * Factory class, used for decoding raw byte arrays, received from baseband,
@@ -88,6 +90,11 @@
     private static final int MAX_GSM7_DEFAULT_CHARS = 239;
     private static final int MAX_UCS2_CHARS = 118;
 
+    // To Report Anomaly
+    public static final UUID NPE_WHEN_CALLED_SEND_CMD_PARAMS_UUID =
+            UUID.fromString("c2b85688-516e-11ee-be56-0242ac120002");
+    public static final String NPE_WHEN_CALLED_SEND_CMD_PARAMS_ERROR_MSG =
+            "mCaller[RilMessageDecoder] is Null when called SendCmdParams";
     /**
      * Returns a singleton instance of CommandParamsFactory
      * @param caller Class used for queuing raw ril messages, decoding them into
@@ -306,7 +313,13 @@
     }
 
     private void sendCmdParams(ResultCode resCode) {
-        mCaller.sendMsgParamsDecoded(resCode, mCmdParams);
+        if (mCaller != null) {
+            mCaller.sendMsgParamsDecoded(resCode, mCmdParams);
+        } else {
+            CatLog.e(this, "mCaller[RilMessageDecoder] is NULL");
+            AnomalyReporter.reportAnomaly(NPE_WHEN_CALLED_SEND_CMD_PARAMS_UUID,
+                    NPE_WHEN_CALLED_SEND_CMD_PARAMS_ERROR_MSG);
+        }
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index ebc6342..2119003 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -129,6 +129,7 @@
         // if sms over IMS is not supported on data and voice is not available...
         if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
             tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
+            notifySmsSentFailedToEmergencyStateTracker(tracker);
             return;
         }
 
diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
index 267f70b..a657ecd 100644
--- a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
@@ -41,6 +41,7 @@
 import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.DataServiceCallback;
 import android.telephony.data.IQualifiedNetworksService;
 import android.telephony.data.IQualifiedNetworksServiceCallback;
 import android.telephony.data.QualifiedNetworksService;
@@ -51,8 +52,11 @@
 import android.util.LocalLog;
 import android.util.SparseArray;
 
+import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.SlidingWindowEventCounter;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.util.FunctionalUtils;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -65,6 +69,7 @@
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -133,6 +138,8 @@
     private final @NonNull Set<AccessNetworksManagerCallback> mAccessNetworksManagerCallbacks =
             new ArraySet<>();
 
+    private final FeatureFlags mFeatureFlags;
+
     /**
      * Represents qualified network types list on a specific APN type.
      */
@@ -294,6 +301,43 @@
                 mQualifiedNetworksChangedRegistrants.notifyResult(qualifiedNetworksList);
             }
         }
+
+        /**
+         * Called when QualifiedNetworksService requests network validation.
+         *
+         * Since the data network in the connected state corresponding to the given network
+         * capability must be validated, a request is tossed to the data network controller.
+         * @param networkCapability network capability
+         */
+        @Override
+        public void onNetworkValidationRequested(@NetCapability int networkCapability,
+                @NonNull IIntegerConsumer resultCodeCallback) {
+            DataNetworkController dnc = mPhone.getDataNetworkController();
+            if (!mFeatureFlags.networkValidation()) {
+                FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+                        .accept(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
+                return;
+            }
+
+            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);
+                        }
+                    });
+                }
+            });
+        }
     }
 
     private void onEmergencyDataNetworkPreferredTransportChanged(
@@ -337,7 +381,8 @@
      * @param phone The phone object.
      * @param looper Looper for the handler.
      */
-    public AccessNetworksManager(@NonNull Phone phone, @NonNull Looper looper) {
+    public AccessNetworksManager(@NonNull Phone phone, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags) {
         super(looper);
         mPhone = phone;
         mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
@@ -346,6 +391,7 @@
         mApnTypeToQnsChangeNetworkCounter = new SparseArray<>();
         mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN};
+        mFeatureFlags = featureFlags;
 
         // bindQualifiedNetworksService posts real work to handler thread. So here we can
         // let the callback execute in binder thread to avoid post twice.
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
index 87591de..02c459a 100644
--- a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
+++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
@@ -23,6 +23,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -34,18 +35,22 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.NetworkRegistrationInfo.RegistrationState;
+import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.NotificationChannelController;
@@ -57,6 +62,11 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * Recommend a data phone to use based on its availability.
@@ -101,7 +111,9 @@
     /** Event for signal strength changed. */
     private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4;
     /** Event indicates the switch state is stable, proceed to validation as the next step. */
-    private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5;
+    private static final int EVENT_STABILITY_CHECK_PASSED = 5;
+    /** Event when subscriptions changed. */
+    private static final int EVENT_SUBSCRIPTIONS_CHANGED = 6;
 
     /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */
     private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
@@ -118,21 +130,60 @@
     /** Notification ID **/
     private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1;
 
+    /**
+     * The threshold of long timer, longer than or equal to which we use alarm manager to schedule
+     * instead of handler.
+     */
+    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;
+    /** A map of a scheduled event to its associated extra for action when the event fires off. */
+    private final @NonNull 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;
+    /**
+     * Event extras for checking environment stability.
+     * @param targetPhoneId The target phone Id to switch to when the stability check pass.
+     * @param isForPerformance Whether the switch is due to RAT/signal strength performance.
+     * @param needValidation Whether ping test needs to pass.
+     */
+    private record StabilityEventExtra(int targetPhoneId, boolean isForPerformance,
+                               boolean needValidation) {}
+
+    /**
+     * Event extras for evaluating switch environment.
+     * @param evaluateReason The reason that triggers the evaluation.
+     */
+    private record EvaluateEventExtra(@AutoDataSwitchEvaluationReason int evaluateReason) {}
     private boolean mDefaultNetworkIsOnNonCellular = false;
     /** {@code true} if we've displayed the notification the first time auto switch occurs **/
     private boolean mDisplayedNotification = false;
     /**
-     * Time threshold in ms to define a internet connection status to be stable(e.g. out of service,
-     * in service, wifi is the default active network.etc), while -1 indicates auto switch
-     * feature disabled.
+     * Configurable time threshold in ms to define an internet connection status to be stable(e.g.
+     * out of service, in service, wifi is the default active network.etc), while -1 indicates auto
+     * switch feature disabled.
      */
     private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1;
     /**
+     * Configurable time threshold in ms to define an internet connection performance status to be
+     * stable (e.g. LTE + 4 signal strength, UMTS + 2 signal strength), while -1 indicates
+     * auto switch feature based on RAT/SS is disabled.
+     */
+    private long mAutoDataSwitchPerformanceStabilityTimeThreshold = -1;
+    /**
+     * The tolerated gap of score for auto data switch decision, larger than which the device will
+     * switch to the SIM with higher score. If 0, the device will always switch to the higher score
+     * SIM. If < 0, the network type and signal strength based auto switch is disabled.
+     */
+    private int mScoreTolerance = -1;
+    /**
      * {@code true} if requires ping test before switching preferred data modem; otherwise, switch
      * even if ping test fails.
      */
@@ -144,36 +195,100 @@
      */
     private int mAutoDataSwitchValidationMaxRetry;
 
+    /** The signal status of phones, where index corresponds to phone Id. */
     private @NonNull PhoneSignalStatus[] mPhonesSignalStatus;
+    /**
+     * The phone Id of the pending switching phone. Used for pruning frequent switch evaluation.
+     */
+    private int mSelectedTargetPhoneId = INVALID_PHONE_INDEX;
 
     /**
      * To track the signal status of a phone in order to evaluate whether it's a good candidate to
      * switch to.
      */
     private static class PhoneSignalStatus {
-        private @NonNull Phone mPhone;
-        private @NetworkRegistrationInfo.RegistrationState int mDataRegState =
-                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
-        private @NonNull TelephonyDisplayInfo mDisplayInfo;
-        private @NonNull SignalStrength mSignalStrength;
-
-        private int mScore;
-
+        /**
+         * How preferred the current phone is.
+         */
+        enum UsableState {
+            HOME(2),
+            ROAMING_ENABLED(1),
+            NON_TERRESTRIAL(0),
+            NOT_USABLE(-1);
+            /**
+             * The higher the score, the more preferred.
+             * HOME is preferred over ROAMING assuming roaming is metered.
+             */
+            final int mScore;
+            UsableState(int score) {
+                this.mScore = score;
+            }
+        }
+        /** The phone */
+        @NonNull private final Phone mPhone;
+        /** Data registration state of the phone */
+        @RegistrationState private int mDataRegState;
+        /** Current Telephony display info of the phone */
+        @NonNull private TelephonyDisplayInfo mDisplayInfo;
+        /** Signal strength of the phone */
+        @NonNull private SignalStrength mSignalStrength;
+        /** {@code true} if this slot is listening for events. */
+        private boolean mListeningForEvents;
         private PhoneSignalStatus(@NonNull Phone phone) {
             this.mPhone = phone;
+            this.mDataRegState = phone.getServiceState().getNetworkRegistrationInfo(
+                            NetworkRegistrationInfo.DOMAIN_PS,
+                            AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                    .getRegistrationState();
             this.mDisplayInfo = phone.getDisplayInfoController().getTelephonyDisplayInfo();
             this.mSignalStrength = phone.getSignalStrength();
         }
-        private int updateScore() {
-            // TODO: score = inservice? dcm.getscore() : 0
-            return mScore;
+
+        /**
+         * @return the current score of this phone. 0 indicates out of service and it will never be
+         * selected as the secondary data candidate.
+         */
+        private int getRatSignalScore() {
+            return isInService(mDataRegState)
+                    ? mPhone.getDataNetworkController().getDataConfigManager()
+                            .getAutoDataSwitchScore(mDisplayInfo, mSignalStrength) : 0;
         }
+
+        /**
+         * @return The current usable state of the phone.
+         */
+        private UsableState getUsableState() {
+            ServiceState serviceState = mPhone.getServiceState();
+            boolean isUsingNonTerrestrialNetwork = sFeatureFlags.carrierEnabledSatelliteFlag()
+                    && (serviceState != null) && serviceState.isUsingNonTerrestrialNetwork();
+
+            switch (mDataRegState) {
+                case NetworkRegistrationInfo.REGISTRATION_STATE_HOME:
+                    if (isUsingNonTerrestrialNetwork) {
+                        return UsableState.NON_TERRESTRIAL;
+                    }
+                    return UsableState.HOME;
+                case NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING:
+                    if (mPhone.getDataRoamingEnabled()) {
+                        if (isUsingNonTerrestrialNetwork) {
+                            return UsableState.NON_TERRESTRIAL;
+                        }
+                        return UsableState.ROAMING_ENABLED;
+                    }
+                    return UsableState.NOT_USABLE;
+                default:
+                    return UsableState.NOT_USABLE;
+            }
+        }
+
         @Override
         public String toString() {
-            return "{phoneId=" + mPhone.getPhoneId()
-                    + " score=" + mScore + " dataRegState="
+            return "{phone " + mPhone.getPhoneId()
+                    + " score=" + getRatSignalScore() + " dataRegState="
                     + NetworkRegistrationInfo.registrationStateToString(mDataRegState)
-                    + " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel()
+                    + " " + getUsableState() + " " + mDisplayInfo
+                    + " signalStrength=" + mSignalStrength.getLevel()
+                    + " listeningForEvents=" + mListeningForEvents
                     + "}";
 
         }
@@ -212,16 +327,22 @@
      * @param phoneSwitcherCallback Callback for phone switcher to execute.
      */
     public AutoDataSwitchController(@NonNull Context context, @NonNull Looper looper,
-            @NonNull PhoneSwitcher phoneSwitcher,
+            @NonNull PhoneSwitcher phoneSwitcher, @NonNull FeatureFlags featureFlags,
             @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) {
         super(looper);
         mContext = context;
+        sFeatureFlags = featureFlags;
+        mPhoneSwitcherCallback = phoneSwitcherCallback;
+        mAlarmManager = context.getSystemService(AlarmManager.class);
+        mScheduledEventsToExtras = new HashMap<>();
+        mEventsToAlarmListener = new HashMap<>();
         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
         mPhoneSwitcher = phoneSwitcher;
-        mPhoneSwitcherCallback = phoneSwitcherCallback;
         readDeviceResourceConfig();
         int numActiveModems = PhoneFactory.getPhones().length;
         mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems];
+        // Listening on all slots on boot up to make sure nothing missed. Later the tracking is
+        // pruned upon subscriptions changed.
         for (int phoneId = 0; phoneId < numActiveModems; phoneId++) {
             registerAllEventsForPhone(phoneId);
         }
@@ -236,16 +357,46 @@
         if (oldActiveModems == numActiveModems) return;
         // Dual -> Single
         for (int phoneId = numActiveModems; phoneId < oldActiveModems; phoneId++) {
-            Phone phone = mPhonesSignalStatus[phoneId].mPhone;
-            phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
-            phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this);
-            phone.getServiceStateTracker().unregisterForServiceStateChanged(this);
+            unregisterAllEventsForPhone(phoneId);
         }
         mPhonesSignalStatus = Arrays.copyOf(mPhonesSignalStatus, numActiveModems);
         // Signal -> Dual
         for (int phoneId = oldActiveModems; phoneId < numActiveModems; phoneId++) {
             registerAllEventsForPhone(phoneId);
         }
+        logl("onMultiSimConfigChanged: " + Arrays.toString(mPhonesSignalStatus));
+    }
+
+    /** Notify subscriptions changed. */
+    public void notifySubscriptionsMappingChanged() {
+        sendEmptyMessage(EVENT_SUBSCRIPTIONS_CHANGED);
+    }
+
+    /**
+     * On subscription changed, register/unregister events on phone Id slot that has active/inactive
+     * sub to reduce unnecessary tracking.
+     */
+    private void onSubscriptionsChanged() {
+        Set<Integer> activePhoneIds = Arrays.stream(mSubscriptionManagerService
+                .getActiveSubIdList(true /*visibleOnly*/))
+                .map(mSubscriptionManagerService::getPhoneId)
+                .boxed()
+                .collect(Collectors.toSet());
+        // Track events only if there are at least two active visible subscriptions.
+        if (activePhoneIds.size() < 2) activePhoneIds.clear();
+        boolean changed = false;
+        for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
+            if (activePhoneIds.contains(phoneId)
+                    && !mPhonesSignalStatus[phoneId].mListeningForEvents) {
+                registerAllEventsForPhone(phoneId);
+                changed = true;
+            } else if (!activePhoneIds.contains(phoneId)
+                    && mPhonesSignalStatus[phoneId].mListeningForEvents) {
+                unregisterAllEventsForPhone(phoneId);
+                changed = true;
+            }
+        }
+        if (changed) logl("onSubscriptionChanged: " + Arrays.toString(mPhonesSignalStatus));
     }
 
     /**
@@ -254,7 +405,7 @@
      */
     private void registerAllEventsForPhone(int phoneId) {
         Phone phone = PhoneFactory.getPhone(phoneId);
-        if (phone != null) {
+        if (phone != null && isActiveModemPhone(phoneId)) {
             mPhonesSignalStatus[phoneId] = new PhoneSignalStatus(phone);
             phone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
                     this, EVENT_DISPLAY_INFO_CHANGED, phoneId);
@@ -262,21 +413,41 @@
                     this, EVENT_SIGNAL_STRENGTH_CHANGED, phoneId);
             phone.getServiceStateTracker().registerForServiceStateChanged(this,
                     EVENT_SERVICE_STATE_CHANGED, phoneId);
+            mPhonesSignalStatus[phoneId].mListeningForEvents = true;
         } else {
             loge("Unexpected null phone " + phoneId + " when register all events");
         }
     }
 
     /**
+     * Unregister all tracking events for a phone.
+     * @param phoneId The phone to unregister for all events.
+     */
+    private void unregisterAllEventsForPhone(int phoneId) {
+        if (isActiveModemPhone(phoneId)) {
+            Phone phone = mPhonesSignalStatus[phoneId].mPhone;
+            phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
+            phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this);
+            phone.getServiceStateTracker().unregisterForServiceStateChanged(this);
+            mPhonesSignalStatus[phoneId].mListeningForEvents = false;
+        } else {
+            loge("Unexpected out of bound phone " + phoneId + " when unregister all events");
+        }
+    }
+
+    /**
      * Read the default device config from any default phone because the resource config are per
      * device. No need to register callback for the same reason.
      */
     private void readDeviceResourceConfig() {
         Phone phone = PhoneFactory.getDefaultPhone();
         DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
+        mScoreTolerance =  dataConfig.getAutoDataSwitchScoreTolerance();
         mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
         mAutoDataSwitchAvailabilityStabilityTimeThreshold =
                 dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        mAutoDataSwitchPerformanceStabilityTimeThreshold =
+                dataConfig.getAutoDataSwitchPerformanceStabilityTimeThreshold();
         mAutoDataSwitchValidationMaxRetry =
                 dataConfig.getAutoDataSwitchValidationMaxRetry();
     }
@@ -289,23 +460,51 @@
             case EVENT_SERVICE_STATE_CHANGED:
                 ar = (AsyncResult) msg.obj;
                 phoneId = (int) ar.userObj;
-                onRegistrationStateChanged(phoneId);
+                onServiceStateChanged(phoneId);
                 break;
             case EVENT_DISPLAY_INFO_CHANGED:
                 ar = (AsyncResult) msg.obj;
                 phoneId = (int) ar.userObj;
                 onDisplayInfoChanged(phoneId);
                 break;
-            case EVENT_EVALUATE_AUTO_SWITCH:
-                int reason = (int) msg.obj;
-                onEvaluateAutoDataSwitch(reason);
+            case EVENT_SIGNAL_STRENGTH_CHANGED:
+                ar = (AsyncResult) msg.obj;
+                phoneId = (int) ar.userObj;
+                onSignalStrengthChanged(phoneId);
                 break;
-            case EVENT_MEETS_AUTO_DATA_SWITCH_STATE:
-                int targetPhoneId = msg.arg1;
-                boolean needValidation = (boolean) msg.obj;
-                log("require validation on phone " + targetPhoneId
-                        + (needValidation ? "" : " no") + " need to pass");
-                mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+            case EVENT_EVALUATE_AUTO_SWITCH:
+                if (sFeatureFlags.autoDataSwitchRatSs()) {
+                    Object obj = mScheduledEventsToExtras.get(EVENT_EVALUATE_AUTO_SWITCH);
+                    if (obj instanceof EvaluateEventExtra extra) {
+                        mScheduledEventsToExtras.remove(EVENT_EVALUATE_AUTO_SWITCH);
+                        onEvaluateAutoDataSwitch(extra.evaluateReason);
+                    }
+                } else {
+                    int reason = (int) msg.obj;
+                    onEvaluateAutoDataSwitch(reason);
+                }
+                break;
+            case EVENT_STABILITY_CHECK_PASSED:
+                if (sFeatureFlags.autoDataSwitchRatSs()) {
+                    Object obj = mScheduledEventsToExtras.get(EVENT_STABILITY_CHECK_PASSED);
+                    if (obj instanceof StabilityEventExtra extra) {
+                        int targetPhoneId = extra.targetPhoneId;
+                        boolean needValidation = extra.needValidation;
+                        log("require validation on phone " + targetPhoneId
+                                + (needValidation ? "" : " no") + " need to pass");
+                        mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
+                        mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+                    }
+                } else {
+                    int targetPhoneId = msg.arg1;
+                    boolean needValidation = msg.arg2 == 1;
+                    log("require validation on phone " + targetPhoneId
+                            + (needValidation ? "" : " no") + " need to pass");
+                    mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+                }
+                break;
+            case EVENT_SUBSCRIPTIONS_CHANGED:
+                onSubscriptionsChanged();
                 break;
             default:
                 loge("Unexpected event " + msg.what);
@@ -315,9 +514,9 @@
     /**
      * Called when registration state changed.
      */
-    private void onRegistrationStateChanged(int phoneId) {
+    private void onServiceStateChanged(int phoneId) {
         Phone phone = PhoneFactory.getPhone(phoneId);
-        if (phone != null) {
+        if (phone != null && isActiveModemPhone(phoneId)) {
             int oldRegState = mPhonesSignalStatus[phoneId].mDataRegState;
             int newRegState = phone.getServiceState()
                     .getNetworkRegistrationInfo(
@@ -326,44 +525,98 @@
                     .getRegistrationState();
             if (newRegState != oldRegState) {
                 mPhonesSignalStatus[phoneId].mDataRegState = newRegState;
-                log("onRegistrationStateChanged: phone " + phoneId + " "
-                        + NetworkRegistrationInfo.registrationStateToString(oldRegState)
-                        + " -> "
-                        + NetworkRegistrationInfo.registrationStateToString(newRegState));
-                evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED);
-            } else {
-                log("onRegistrationStateChanged: no change.");
+                if (isInService(oldRegState) != isInService(newRegState)
+                        || isHomeService(oldRegState) != isHomeService(newRegState)) {
+                    log("onServiceStateChanged: phone " + phoneId + " "
+                            + NetworkRegistrationInfo.registrationStateToString(oldRegState)
+                            + " -> "
+                            + NetworkRegistrationInfo.registrationStateToString(newRegState));
+                    evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED);
+                }
             }
         } else {
             loge("Unexpected null phone " + phoneId + " upon its registration state changed");
         }
     }
 
-    /**
-     * @return {@code true} if the phone state is considered in service.
-     */
-    private boolean isInService(@NetworkRegistrationInfo.RegistrationState int dataRegState) {
+    /** @return {@code true} if the phone state is considered in service. */
+    private static boolean isInService(@RegistrationState int dataRegState) {
         return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
                 || dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
     }
 
+    /** @return {@code true} if the phone state is in home service. */
+    private static boolean isHomeService(@RegistrationState int dataRegState) {
+        return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+    }
+
     /**
      * Called when {@link TelephonyDisplayInfo} changed. This can happen when network types or
      * override network types (5G NSA, 5G MMWAVE) change.
+     * @param phoneId The phone that changed.
      */
     private void onDisplayInfoChanged(int phoneId) {
         Phone phone = PhoneFactory.getPhone(phoneId);
-        if (phone != null) {
+        if (phone != null && isActiveModemPhone(phoneId)) {
             TelephonyDisplayInfo displayInfo = phone.getDisplayInfoController()
                     .getTelephonyDisplayInfo();
-            //TODO(b/260928808)
-            log("onDisplayInfoChanged:" + displayInfo);
+            mPhonesSignalStatus[phoneId].mDisplayInfo = displayInfo;
+            if (getHigherScoreCandidatePhoneId() != mSelectedTargetPhoneId) {
+                log("onDisplayInfoChanged: phone " + phoneId + " " + displayInfo);
+                evaluateAutoDataSwitch(EVALUATION_REASON_DISPLAY_INFO_CHANGED);
+            }
         } else {
             loge("Unexpected null phone " + phoneId + " upon its display info changed");
         }
     }
 
     /**
+     * Called when {@link SignalStrength} changed.
+     * @param phoneId The phone that changed.
+     */
+    private void onSignalStrengthChanged(int phoneId) {
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        if (phone != null && isActiveModemPhone(phoneId)) {
+            SignalStrength newSignalStrength = phone.getSignalStrength();
+            SignalStrength oldSignalStrength = mPhonesSignalStatus[phoneId].mSignalStrength;
+            if (oldSignalStrength.getLevel() != newSignalStrength.getLevel()) {
+                mPhonesSignalStatus[phoneId].mSignalStrength = newSignalStrength;
+                if (getHigherScoreCandidatePhoneId() != mSelectedTargetPhoneId) {
+                    log("onSignalStrengthChanged: phone " + phoneId + " "
+                            + oldSignalStrength.getLevel() + "->" + newSignalStrength.getLevel());
+                    evaluateAutoDataSwitch(EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED);
+                }
+            }
+        } else {
+            loge("Unexpected null phone " + phoneId + " upon its signal strength changed");
+        }
+    }
+
+    /**
+     * Called as a preliminary check for the frequent signal/display info change.
+     * @return The phone Id if found a candidate phone with higher signal score, or the DDS has
+     * an equal score.
+     */
+    private int getHigherScoreCandidatePhoneId() {
+        int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
+        int ddsPhoneId = mSubscriptionManagerService.getPhoneId(
+                mSubscriptionManagerService.getDefaultDataSubId());
+        if (isActiveModemPhone(preferredPhoneId) && isActiveModemPhone(ddsPhoneId)) {
+            int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
+            for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
+                if (phoneId == preferredPhoneId) continue;
+                int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore();
+                if ((candidateScore - currentScore) > mScoreTolerance
+                        // Also reevaluate if DDS has the same score as the current phone.
+                        || (candidateScore >= currentScore && phoneId == ddsPhoneId)) {
+                    return phoneId;
+                }
+            }
+        }
+        return INVALID_PHONE_INDEX;
+    }
+
+    /**
      * Schedule for auto data switch evaluation.
      * @param reason The reason for the evaluation.
      */
@@ -372,8 +625,15 @@
                 ? mAutoDataSwitchAvailabilityStabilityTimeThreshold
                 << mAutoSwitchValidationFailedCount
                 : 0;
-        if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
-            sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs);
+        if (sFeatureFlags.autoDataSwitchRatSs()) {
+            if (!mScheduledEventsToExtras.containsKey(EVENT_EVALUATE_AUTO_SWITCH)) {
+                scheduleEventWithTimer(EVENT_EVALUATE_AUTO_SWITCH, new EvaluateEventExtra(reason),
+                        delayMs);
+            }
+        } else {
+            if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
+                sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs);
+            }
         }
     }
 
@@ -388,114 +648,274 @@
         if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return;
         int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
         // check is valid DSDS
-        if (!isActiveSubId(defaultDataSubId) || mSubscriptionManagerService
-                .getActiveSubIdList(true).length <= 1) {
-            return;
-        }
-        Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId(
-                defaultDataSubId));
+        if (mSubscriptionManagerService.getActiveSubIdList(true).length < 2) return;
+        int defaultDataPhoneId = mSubscriptionManagerService.getPhoneId(
+                defaultDataSubId);
+        Phone defaultDataPhone = PhoneFactory.getPhone(defaultDataPhoneId);
         if (defaultDataPhone == null) {
             loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data"
                     + " subscription " + defaultDataSubId);
             return;
         }
-        int defaultDataPhoneId = defaultDataPhone.getPhoneId();
+
         int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId();
-        log("onEvaluateAutoDataSwitch: defaultPhoneId: " + defaultDataPhoneId
-                + " preferredPhoneId: " + preferredPhoneId
-                + " reason: " + evaluationReasonToString(reason));
+        StringBuilder debugMessage = new StringBuilder("onEvaluateAutoDataSwitch:");
+        debugMessage.append(" defaultPhoneId: ").append(defaultDataPhoneId)
+                .append(" preferredPhoneId: ").append(preferredPhoneId)
+                .append(", reason: ").append(evaluationReasonToString(reason));
         if (preferredPhoneId == defaultDataPhoneId) {
             // on default data sub
-            int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId);
-            if (candidatePhoneId != INVALID_PHONE_INDEX) {
-                startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch);
+            StabilityEventExtra res = evaluateAnyCandidateToUse(defaultDataPhoneId, debugMessage);
+            log(debugMessage.toString());
+            if (res.targetPhoneId != INVALID_PHONE_INDEX) {
+                mSelectedTargetPhoneId = res.targetPhoneId;
+                startStabilityCheck(res.targetPhoneId, res.isForPerformance, res.needValidation);
             } else {
                 cancelAnyPendingSwitch();
             }
         } else {
             // on backup data sub
             Phone backupDataPhone = PhoneFactory.getPhone(preferredPhoneId);
-            if (backupDataPhone == null) {
-                loge("onEvaluateAutoDataSwitch: Unexpected null phone " + preferredPhoneId
-                        + " as the current active data phone");
+            if (backupDataPhone == null || !isActiveModemPhone(preferredPhoneId)) {
+                loge(debugMessage.append(" Unexpected null phone ").append(preferredPhoneId)
+                        .append(" as the current active data phone").toString());
                 return;
             }
 
             if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) {
-                // immediately switch back if user disabled setting changes
                 mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
                         EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+                log(debugMessage.append(", immediately back to default as user turns off settings")
+                        .toString());
                 return;
             }
 
-            if (mDefaultNetworkIsOnNonCellular) {
-                log("onEvaluateAutoDataSwitch: Default network is active on nonCellular transport");
-                startStabilityCheck(DEFAULT_PHONE_INDEX, false);
-                return;
+            boolean backToDefault = false;
+            boolean isForPerformance = false;
+            boolean needValidation = true;
+
+            if (sFeatureFlags.autoSwitchAllowRoaming()) {
+                if (mDefaultNetworkIsOnNonCellular) {
+                    debugMessage.append(", back to default as default network")
+                            .append(" is active on nonCellular transport");
+                    backToDefault = true;
+                    needValidation = false;
+                } else {
+                    PhoneSignalStatus.UsableState defaultUsableState =
+                            mPhonesSignalStatus[defaultDataPhoneId].getUsableState();
+                    PhoneSignalStatus.UsableState currentUsableState =
+                            mPhonesSignalStatus[preferredPhoneId].getUsableState();
+
+                    boolean isCurrentUsable = currentUsableState.mScore
+                            > PhoneSignalStatus.UsableState.NOT_USABLE.mScore;
+
+                    if (currentUsableState.mScore < defaultUsableState.mScore) {
+                        debugMessage.append(", back to default phone ").append(preferredPhoneId)
+                                .append(" : ").append(defaultUsableState)
+                                .append(" , backup phone: ").append(currentUsableState);
+
+                        backToDefault = true;
+                        // Require validation if the current preferred phone is usable.
+                        needValidation = isCurrentUsable && mRequirePingTestBeforeSwitch;
+                    } else if (defaultUsableState.mScore == currentUsableState.mScore) {
+                        debugMessage.append(", default phone ").append(preferredPhoneId)
+                                .append(" : ").append(defaultUsableState)
+                                .append(" , backup phone: ").append(currentUsableState);
+
+                        if (isCurrentUsable) {
+                            // Both phones are usable.
+                            if (isRatSignalStrengthBasedSwitchEnabled()) {
+                                int defaultScore = mPhonesSignalStatus[defaultDataPhoneId]
+                                        .getRatSignalScore();
+                                int currentScore = mPhonesSignalStatus[preferredPhoneId]
+                                        .getRatSignalScore();
+                                if (defaultScore >= currentScore) {
+                                    debugMessage
+                                            .append(", back to default for higher or equal score ")
+                                            .append(defaultScore).append(" versus current ")
+                                            .append(currentScore);
+                                    backToDefault = true;
+                                    isForPerformance = true;
+                                    needValidation = mRequirePingTestBeforeSwitch;
+                                }
+                            } else {
+                                // Only OOS/in service switch is enabled, switch back.
+                                debugMessage.append(", back to default as it's usable. ");
+                                backToDefault = true;
+                                needValidation = mRequirePingTestBeforeSwitch;
+                            }
+                        } else {
+                            debugMessage.append(", back to default as both phones are unusable.");
+                            backToDefault = true;
+                            needValidation = false;
+                        }
+                    }
+                }
+            } else {
+                if (mDefaultNetworkIsOnNonCellular) {
+                    debugMessage.append(", back to default as default network")
+                            .append(" is active on nonCellular transport");
+                    backToDefault = true;
+                    needValidation = false;
+                } else if (!isHomeService(mPhonesSignalStatus[preferredPhoneId].mDataRegState)) {
+                    debugMessage.append(", back to default as backup phone lost HOME registration");
+                    backToDefault = true;
+                    needValidation = false;
+                } else if (isRatSignalStrengthBasedSwitchEnabled()) {
+                    int defaultScore = mPhonesSignalStatus[defaultDataPhoneId].getRatSignalScore();
+                    int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
+                    if (defaultScore >= currentScore) {
+                        debugMessage
+                                .append(", back to default as default has higher or equal score ")
+                                .append(defaultScore).append(" versus current ")
+                                .append(currentScore);
+                        backToDefault = true;
+                        isForPerformance = true;
+                        needValidation = mRequirePingTestBeforeSwitch;
+                    }
+                } else if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
+                    debugMessage.append(", back to default as the default is back to service ");
+                    backToDefault = true;
+                    needValidation = mRequirePingTestBeforeSwitch;
+                }
             }
 
-            if (mPhonesSignalStatus[preferredPhoneId].mDataRegState
-                    != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
-                // backup phone lost its HOME registration
-                startStabilityCheck(DEFAULT_PHONE_INDEX, false);
-                return;
+            if (backToDefault) {
+                log(debugMessage.toString());
+                mSelectedTargetPhoneId = defaultDataPhoneId;
+                startStabilityCheck(DEFAULT_PHONE_INDEX, isForPerformance, needValidation);
+            } else {
+                // cancel any previous attempts of switching back to default phone
+                cancelAnyPendingSwitch();
             }
-
-            if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
-                // default phone is back to service
-                startStabilityCheck(DEFAULT_PHONE_INDEX, mRequirePingTestBeforeSwitch);
-                return;
-            }
-
-            // cancel any previous attempts of switching back to default phone
-            cancelAnyPendingSwitch();
         }
     }
 
     /**
      * Called when consider switching from primary default data sub to another data sub.
-     * @return the target subId if a suitable candidate is found, otherwise return
-     * {@link SubscriptionManager#INVALID_PHONE_INDEX}
+     * @param defaultPhoneId The default data phone
+     * @param debugMessage Debug message.
+     * @return StabilityEventExtra As evaluation result.
      */
-    private int getSwitchCandidatePhoneId(int defaultPhoneId) {
+    @NonNull private StabilityEventExtra evaluateAnyCandidateToUse(int defaultPhoneId,
+            @NonNull StringBuilder debugMessage) {
         Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId);
+        boolean isForPerformance = false;
+        StabilityEventExtra invalidResult = new StabilityEventExtra(INVALID_PHONE_INDEX,
+                isForPerformance, mRequirePingTestBeforeSwitch);
+
         if (defaultDataPhone == null) {
-            log("getSwitchCandidatePhoneId: no sim loaded");
-            return INVALID_PHONE_INDEX;
+            debugMessage.append(", no candidate as no sim loaded");
+            return invalidResult;
         }
 
         if (!defaultDataPhone.isUserDataEnabled()) {
-            log("getSwitchCandidatePhoneId: user disabled data");
-            return INVALID_PHONE_INDEX;
+            debugMessage.append(", no candidate as user disabled mobile data");
+            return invalidResult;
         }
 
         if (mDefaultNetworkIsOnNonCellular) {
-            // Exists other active default transport
-            log("getSwitchCandidatePhoneId: Default network is active on non-cellular transport");
-            return INVALID_PHONE_INDEX;
+            debugMessage.append(", no candidate as default network is active")
+                    .append(" on non-cellular transport");
+            return invalidResult;
         }
 
-        // check whether primary and secondary signal status are worth switching
-        if (isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
-            log("getSwitchCandidatePhoneId: DDS is in service");
-            return INVALID_PHONE_INDEX;
+        if (sFeatureFlags.autoSwitchAllowRoaming()) {
+            // check whether primary and secondary signal status are worth switching
+            if (!isRatSignalStrengthBasedSwitchEnabled()
+                    && isHomeService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
+                debugMessage.append(", no candidate as default phone is in HOME service");
+                return invalidResult;
+            }
+        } else {
+            // check whether primary and secondary signal status are worth switching
+            if (!isRatSignalStrengthBasedSwitchEnabled()
+                    && isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) {
+                debugMessage.append(", no candidate as default phone is in service");
+                return invalidResult;
+            }
         }
+
+        PhoneSignalStatus defaultPhoneStatus = mPhonesSignalStatus[defaultPhoneId];
         for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) {
-            if (phoneId != defaultPhoneId) {
-                // the alternative phone must have HOME availability
-                if (mPhonesSignalStatus[phoneId].mDataRegState
-                        == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
-                    log("getSwitchCandidatePhoneId: found phone " + phoneId
-                            + " in HOME service");
-                    Phone secondaryDataPhone = PhoneFactory.getPhone(phoneId);
-                    if (secondaryDataPhone != null && // check auto switch feature enabled
-                            secondaryDataPhone.isDataAllowed()) {
-                        return phoneId;
+            if (phoneId == defaultPhoneId) continue;
+
+            Phone secondaryDataPhone = null;
+            PhoneSignalStatus candidatePhoneStatus = mPhonesSignalStatus[phoneId];
+            if (sFeatureFlags.autoSwitchAllowRoaming()) {
+                PhoneSignalStatus.UsableState currentUsableState =
+                        mPhonesSignalStatus[defaultPhoneId].getUsableState();
+                PhoneSignalStatus.UsableState candidateUsableState =
+                        mPhonesSignalStatus[phoneId].getUsableState();
+                debugMessage.append(", found phone ").append(phoneId).append(" ")
+                        .append(candidateUsableState)
+                        .append(", default is ").append(currentUsableState);
+                if (candidateUsableState.mScore > currentUsableState.mScore) {
+                    secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+                } else if (isRatSignalStrengthBasedSwitchEnabled()
+                        && currentUsableState.mScore == candidateUsableState.mScore) {
+                    // Both phones are home or both roaming enabled, so compare RAT/signal score.
+
+                    int defaultScore = defaultPhoneStatus.getRatSignalScore();
+                    int candidateScore = candidatePhoneStatus.getRatSignalScore();
+                    if ((candidateScore - defaultScore) > mScoreTolerance) {
+                        debugMessage.append(" with ").append(defaultScore)
+                                .append(" versus candidate higher score ").append(candidateScore);
+                        secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+                        isForPerformance = true;
+                    } else {
+                        debugMessage.append(", candidate's score ").append(candidateScore)
+                                .append(" doesn't justify the switch given the current ")
+                                .append(defaultScore);
                     }
                 }
+            } else if (isHomeService(candidatePhoneStatus.mDataRegState)) {
+                // the alternative phone must have HOME availability
+                debugMessage.append(", found phone ").append(phoneId).append(" in HOME service");
+
+                if (isInService(defaultPhoneStatus.mDataRegState)) {
+                    // Use score if RAT/signal strength based switch is enabled and both phone are
+                    // in service.
+                    if (isRatSignalStrengthBasedSwitchEnabled()) {
+                        int defaultScore = mPhonesSignalStatus[defaultPhoneId].getRatSignalScore();
+                        int candidateScore = mPhonesSignalStatus[phoneId].getRatSignalScore();
+                        if ((candidateScore - defaultScore) > mScoreTolerance) {
+                            debugMessage.append(" with higher score ").append(candidateScore)
+                                    .append(" versus current ").append(defaultScore);
+                            secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+                            isForPerformance = true;
+                        } else {
+                            debugMessage.append(", but its score ").append(candidateScore)
+                                    .append(" doesn't meet the bar to switch given the current ")
+                                    .append(defaultScore);
+                        }
+                    }
+                } else {
+                    // Only OOS/in service switch is enabled.
+                    secondaryDataPhone = PhoneFactory.getPhone(phoneId);
+                }
+            }
+
+            if (secondaryDataPhone != null) {
+                // check auto switch feature enabled
+                if (secondaryDataPhone.isDataAllowed()) {
+                    return new StabilityEventExtra(phoneId,
+                            isForPerformance, mRequirePingTestBeforeSwitch);
+                } else {
+                    debugMessage.append(", but candidate's data is not allowed");
+                }
             }
         }
-        return INVALID_PHONE_INDEX;
+        debugMessage.append(", found no qualified candidate.");
+        return invalidResult;
+    }
+
+    /**
+     * @return {@code true} If the feature of switching base on RAT and signal strength is enabled.
+     */
+    private boolean isRatSignalStrengthBasedSwitchEnabled() {
+        return sFeatureFlags.autoDataSwitchRatSs() && mScoreTolerance >= 0
+                && mAutoDataSwitchPerformanceStabilityTimeThreshold >= 0;
     }
 
     /**
@@ -503,16 +923,77 @@
      * Start pre-switch validation if the current environment suits auto data switch for
      * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
      * @param targetPhoneId the target phone Id.
+     * @param isForPerformance {@code true} entails longer stability check.
      * @param needValidation {@code true} if validation is needed.
      */
-    private void startStabilityCheck(int targetPhoneId, boolean needValidation) {
-        log("startAutoDataSwitchStabilityCheck: targetPhoneId=" + targetPhoneId
-                + " needValidation=" + needValidation);
-        if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) {
-            sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId,
-                            0/*placeholder*/,
-                            needValidation),
+    private void startStabilityCheck(int targetPhoneId, boolean isForPerformance,
+            boolean needValidation) {
+        String combinationIdentifier = targetPhoneId + "" + needValidation;
+        if (sFeatureFlags.autoDataSwitchRatSs()) {
+            StabilityEventExtra eventExtras = (StabilityEventExtra)
+                    mScheduledEventsToExtras.getOrDefault(EVENT_STABILITY_CHECK_PASSED,
+                            new StabilityEventExtra(INVALID_PHONE_INDEX, false /*need validation*/,
+                            false /*isForPerformance*/));
+            long delayMs = -1;
+            // Check if already scheduled one with that combination of extras.
+            if (eventExtras.targetPhoneId != targetPhoneId
+                    || eventExtras.needValidation != needValidation
+                    || eventExtras.isForPerformance != isForPerformance) {
+                eventExtras =
+                        new StabilityEventExtra(targetPhoneId, isForPerformance, needValidation);
+
+                // Reset with new timer.
+                delayMs = isForPerformance
+                        ? mAutoDataSwitchPerformanceStabilityTimeThreshold
+                        : mAutoDataSwitchAvailabilityStabilityTimeThreshold;
+                scheduleEventWithTimer(EVENT_STABILITY_CHECK_PASSED, eventExtras, delayMs);
+            }
+            log("startStabilityCheck: "
+                    + (delayMs != -1 ? "scheduling " : "already scheduled ")
+                    + eventExtras);
+        } else if (!hasEqualMessages(EVENT_STABILITY_CHECK_PASSED, combinationIdentifier)) {
+            removeMessages(EVENT_STABILITY_CHECK_PASSED);
+            sendMessageDelayed(obtainMessage(EVENT_STABILITY_CHECK_PASSED, targetPhoneId,
+                            needValidation ? 1 : 0,
+                            combinationIdentifier),
                     mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+            log("startStabilityCheck: targetPhoneId=" + targetPhoneId
+                    + " isForPerformance=" + isForPerformance
+                    + " needValidation=" + needValidation);
+        }
+    }
+
+    /**
+     * Use when need to schedule with timer. Short timer uses handler, while the longer timer uses
+     * alarm manager to account for real time elapse.
+     *
+     * @param event The event.
+     * @param extras Any extra data associated with the event.
+     * @param delayMs The delayed interval in ms.
+     */
+    private void scheduleEventWithTimer(int event, @NonNull Object extras, long delayMs) {
+        // Get singleton alarm listener.
+        mEventsToAlarmListener.putIfAbsent(event, () -> sendEmptyMessage(event));
+        AlarmManager.OnAlarmListener listener = mEventsToAlarmListener.get(event);
+
+        // Cancel any existing.
+        removeMessages(event);
+        mAlarmManager.cancel(listener);
+        // Override with new extras.
+        mScheduledEventsToExtras.put(event, extras);
+        // Reset timer.
+        if (delayMs <= RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS) {
+            // Use handler for short timer.
+            sendEmptyMessageDelayed(event, delayMs);
+        } else {
+            // Not using setWhileIdle because it can wait util the next time the device wakes up to
+            // save power.
+            // If another evaluation is processed before the alarm fires,
+            // this timer is restarted (AlarmManager using the same listener resets the
+            // timer).
+            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
+                    SystemClock.elapsedRealtime() + delayMs,
+                    LOG_TAG /*debug tag*/, listener, this);
         }
     }
 
@@ -566,8 +1047,21 @@
      * Cancel any auto switch attempts when the current environment is not suitable for auto switch.
      */
     private void cancelAnyPendingSwitch() {
+        mSelectedTargetPhoneId = INVALID_PHONE_INDEX;
         resetFailedCount();
-        removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
+        if (sFeatureFlags.autoDataSwitchRatSs()) {
+            if (mScheduledEventsToExtras.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+                if (mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+                    mAlarmManager.cancel(mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED));
+                } else {
+                    loge("cancelAnyPendingSwitch: EVENT_STABILITY_CHECK_PASSED listener is null");
+                }
+                removeMessages(EVENT_STABILITY_CHECK_PASSED);
+                mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
+            }
+        } else {
+            removeMessages(EVENT_STABILITY_CHECK_PASSED);
+        }
         mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation();
     }
 
@@ -581,8 +1075,6 @@
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         if (mDisplayedNotification) {
             // cancel posted notification if any exist
-            log("displayAutoDataSwitchNotification: canceling any notifications for phone "
-                    + phoneId);
             notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
                     AUTO_DATA_SWITCH_NOTIFICATION_ID);
             return;
@@ -651,6 +1143,14 @@
     }
 
     /**
+     * @param phoneId The phone Id to check.
+     * @return {@code true} if the phone Id is an active modem.
+     */
+    private boolean isActiveModemPhone(int phoneId) {
+        return phoneId >= 0 && phoneId < mPhonesSignalStatus.length;
+    }
+
+    /**
      * Log debug messages.
      * @param s debug messages
      */
@@ -686,11 +1186,13 @@
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println("AutoDataSwitchController:");
         pw.increaseIndent();
+        pw.println("mScoreTolerance=" + mScoreTolerance);
         pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry
                 + " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount);
         pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch);
         pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
                 + mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+        pw.println("mSelectedTargetPhoneId=" + mSelectedTargetPhoneId);
         pw.increaseIndent();
         for (PhoneSignalStatus status: mPhonesSignalStatus) {
             pw.println(status);
diff --git a/src/java/com/android/internal/telephony/data/CellularDataService.java b/src/java/com/android/internal/telephony/data/CellularDataService.java
index c5923aa..80d6b53 100644
--- a/src/java/com/android/internal/telephony/data/CellularDataService.java
+++ b/src/java/com/android/internal/telephony/data/CellularDataService.java
@@ -167,6 +167,7 @@
                 boolean isRoaming, boolean allowRoaming, int reason, LinkProperties linkProperties,
                 int pduSessionId, NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
                 boolean matchAllRuleAllowed, DataServiceCallback callback) {
+            // TODO: remove isRoaming parameter
             if (DBG) log("setupDataCall " + getSlotIndex());
 
             Message message = null;
@@ -177,9 +178,9 @@
                 mCallbackMap.put(message, callback);
             }
 
-            mPhone.mCi.setupDataCall(accessNetworkType, dataProfile, isRoaming, allowRoaming,
-                    reason, linkProperties, pduSessionId, sliceInfo, trafficDescriptor,
-                    matchAllRuleAllowed, message);
+            mPhone.mCi.setupDataCall(accessNetworkType, dataProfile, allowRoaming, reason,
+                    linkProperties, pduSessionId, sliceInfo, trafficDescriptor, matchAllRuleAllowed,
+                    message);
         }
 
         @Override
@@ -199,7 +200,8 @@
 
         @Override
         public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming,
-                                        DataServiceCallback callback) {
+                DataServiceCallback callback) {
+            // TODO: remove isRoaming parameter
             if (DBG) log("setInitialAttachApn " + getSlotIndex());
 
             Message message = null;
@@ -210,12 +212,13 @@
                 mCallbackMap.put(message, callback);
             }
 
-            mPhone.mCi.setInitialAttachApn(dataProfile, isRoaming, message);
+            mPhone.mCi.setInitialAttachApn(dataProfile, message);
         }
 
         @Override
         public void setDataProfile(List<DataProfile> dps, boolean isRoaming,
-                                   DataServiceCallback callback) {
+                DataServiceCallback callback) {
+            // TODO: remove isRoaming parameter
             if (DBG) log("setDataProfile " + getSlotIndex());
 
             Message message = null;
@@ -226,7 +229,7 @@
                 mCallbackMap.put(message, callback);
             }
 
-            mPhone.mCi.setDataProfile(dps.toArray(new DataProfile[dps.size()]), isRoaming, message);
+            mPhone.mCi.setDataProfile(dps.toArray(new DataProfile[dps.size()]), message);
         }
 
         @Override
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 832eb61..90743f8 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -31,6 +31,7 @@
 import android.telephony.Annotation.NetCapability;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
@@ -46,6 +47,7 @@
 import com.android.internal.telephony.data.DataNetworkController.HandoverRule;
 import com.android.internal.telephony.data.DataRetryManager.DataHandoverRetryRule;
 import com.android.internal.telephony.data.DataRetryManager.DataSetupRetryRule;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -213,8 +215,8 @@
             "anomaly_network_handover_timeout";
     /** DeviceConfig key of anomaly report: True for enabling APN config invalidity detection */
     private static final String KEY_ANOMALY_APN_CONFIG_ENABLED = "anomaly_apn_config_enabled";
-    /** Invalid auto data switch score. */
-    private static final int INVALID_AUTO_DATA_SWITCH_SCORE = -1;
+    /** Placeholder indicating missing Auto data switch score config, meaning out of service. */
+    private static final int OUT_OF_SERVICE_AUTO_DATA_SWITCH_SCORE = 0;
     /** Anomaly report thresholds for frequent setup data call failure. */
     private EventFrequency mSetupDataCallAnomalyReportThreshold;
 
@@ -259,6 +261,7 @@
     private @NonNull final Phone mPhone;
     private @NonNull final String mLogTag;
 
+    @NonNull private final FeatureFlags mFeatureFlags;
     private @NonNull final CarrierConfigManager mCarrierConfigManager;
     private @NonNull PersistableBundle mCarrierConfig = null;
     private @NonNull Resources mResources = null;
@@ -295,6 +298,9 @@
     private @NonNull final List<HandoverRule> mHandoverRuleList = new ArrayList<>();
     /** {@code True} keep IMS network in case of moving to non VOPS area; {@code false} otherwise.*/
     private boolean mShouldKeepNetworkUpInNonVops = false;
+    /** The set of network types that enable VOPS even in non VOPS area. */
+    @NonNull private final @CarrierConfigManager.Ims.NetworkType List<Integer>
+            mEnabledVopsNetworkTypesInNonVops = new ArrayList<>();
     /**
      * A map of network types to the estimated downlink values by signal strength 0 - 4 for that
      * network type
@@ -309,9 +315,11 @@
      * @param looper The looper to be used by the handler. Currently the handler thread is the
      * phone process's main thread.
      */
-    public DataConfigManager(@NonNull Phone phone, @NonNull Looper looper) {
+    public DataConfigManager(@NonNull Phone phone, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags) {
         super(looper);
         mPhone = phone;
+        mFeatureFlags = featureFlags;
         mLogTag = "DCM-" + mPhone.getPhoneId();
         log("DataConfigManager created.");
 
@@ -326,7 +334,7 @@
 
         // Register for device config update
         DeviceConfig.addOnPropertiesChangedListener(
-                DeviceConfig.NAMESPACE_TELEPHONY, this::post,
+                DeviceConfig.NAMESPACE_TELEPHONY, Runnable::run,
                 properties -> {
                     if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY,
                             properties.getNamespace())) {
@@ -590,10 +598,20 @@
      */
     public @NonNull @NetCapability Set<Integer> getMeteredNetworkCapabilities(boolean isRoaming) {
         Set<Integer> meteredApnTypes = isRoaming ? mRoamingMeteredApnTypes : mMeteredApnTypes;
-        return meteredApnTypes.stream()
+        Set<Integer> meteredCapabilities = meteredApnTypes.stream()
                 .map(DataUtils::apnTypeToNetworkCapability)
                 .filter(cap -> cap >= 0)
-                .collect(Collectors.toUnmodifiableSet());
+                .collect(Collectors.toSet());
+
+        // Consumer slices are the slices that are allowed to be accessed by regular application to
+        // get better performance. They should be metered. This can be turned into configurations in
+        // the future.
+        if (mFeatureFlags.meteredEmbbUrlcc()) {
+            meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+            meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
+        }
+
+        return Collections.unmodifiableSet(meteredCapabilities);
     }
 
     /**
@@ -666,6 +684,11 @@
         synchronized (this) {
             mShouldKeepNetworkUpInNonVops = mCarrierConfig.getBoolean(CarrierConfigManager
                     .Ims.KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL);
+            int[] allowedNetworkTypes = mCarrierConfig.getIntArray(
+                    CarrierConfigManager.Ims.KEY_IMS_PDN_ENABLED_IN_NO_VOPS_SUPPORT_INT_ARRAY);
+            if (allowedNetworkTypes != null) {
+                Arrays.stream(allowedNetworkTypes).forEach(mEnabledVopsNetworkTypesInNonVops::add);
+            }
         }
     }
 
@@ -684,9 +707,29 @@
         return Collections.unmodifiableSet(mCapabilitiesExemptFromSingleDataList);
     }
 
-    /** {@code True} keep IMS network in case of moving to non VOPS area; {@code false} otherwise.*/
-    public boolean shouldKeepNetworkUpInNonVops() {
-        return mShouldKeepNetworkUpInNonVops;
+    /**
+     * @param regState The modem reported data registration state.
+     * @return {@code true} if should keep IMS network in case of moving to non VOPS area.
+     */
+    public boolean shouldKeepNetworkUpInNonVops(@NetworkRegistrationInfo.RegistrationState
+            int regState) {
+        return mShouldKeepNetworkUpInNonVops || allowBringUpNetworkInNonVops(regState);
+    }
+
+    /**
+     * @param regState The modem reported data registration state.
+     * @return {@code true} if allow bring up IMS network in case of moving to non VOPS area.
+     */
+    public boolean allowBringUpNetworkInNonVops(@NetworkRegistrationInfo.RegistrationState
+            int regState) {
+        if (!mFeatureFlags.allowMmtelInNonVops()) return false;
+        int networkType = -1;
+        if (regState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
+            networkType = CarrierConfigManager.Ims.NETWORK_TYPE_HOME;
+        } else if (regState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) {
+            networkType = CarrierConfigManager.Ims.NETWORK_TYPE_ROAMING;
+        }
+        return mEnabledVopsNetworkTypesInNonVops.contains(networkType);
     }
 
     /** {@code True} requires ping test to pass on the target slot before switching to it.*/
@@ -827,6 +870,14 @@
     }
 
     /**
+     * @return the data limit in bytes that can be used for esim bootstrap usage.
+     */
+    public long getEsimBootStrapMaxDataLimitBytes() {
+        return mResources.getInteger(
+                com.android.internal.R.integer.config_esim_bootstrap_data_limit_bytes);
+    }
+
+    /**
      * Update the TCP buffer sizes from the resource overlays.
      */
     private void updateTcpBuffers() {
@@ -941,6 +992,7 @@
                     DATA_CONFIG_NETWORK_TYPE_EHRPD,
                     DATA_CONFIG_NETWORK_TYPE_IDEN,
                     DATA_CONFIG_NETWORK_TYPE_LTE,
+                    DATA_CONFIG_NETWORK_TYPE_LTE_CA,
                     DATA_CONFIG_NETWORK_TYPE_HSPAP,
                     DATA_CONFIG_NETWORK_TYPE_GSM,
                     DATA_CONFIG_NETWORK_TYPE_TD_SCDMA,
@@ -978,12 +1030,14 @@
      * @param displayInfo The displayed network info.
      * @param signalStrength The signal strength.
      * @return Score base on network type and signal strength to inform auto data switch decision.
+     * The min score is {@link #OUT_OF_SERVICE_AUTO_DATA_SWITCH_SCORE} indicating missing config.
      */
     public int getAutoDataSwitchScore(@NonNull TelephonyDisplayInfo displayInfo,
             @NonNull SignalStrength signalStrength) {
         int[] scores = mAutoDataSwitchNetworkTypeSignalMap.get(
                 getDataConfigNetworkType(displayInfo));
-        return scores != null ? scores[signalStrength.getLevel()] : INVALID_AUTO_DATA_SWITCH_SCORE;
+        return scores != null ? scores[signalStrength.getLevel()]
+                : OUT_OF_SERVICE_AUTO_DATA_SWITCH_SCORE;
     }
 
     /**
@@ -1015,6 +1069,16 @@
     }
 
     /**
+     * @return Time threshold in ms to define a internet connection performance status to be stable
+     * (e.g. LTE + 4 signal strength, UMTS + 2 signal strength), while -1 indicates
+     * auto switch feature based on RAT/SS is disabled.
+     */
+    public long getAutoDataSwitchPerformanceStabilityTimeThreshold() {
+        return mResources.getInteger(com.android.internal.R.integer
+                .auto_data_switch_performance_stability_time_threshold_millis);
+    }
+
+    /**
      * Get the TCP config string, used by {@link LinkProperties#setTcpBufferSizes(String)}.
      * The config string will have the following form, with values in bytes:
      * "read_min,read_default,read_max,write_min,write_default,write_max"
@@ -1423,6 +1487,8 @@
                 + Arrays.toString(value)));
         pw.println("getAutoDataSwitchAvailabilityStabilityTimeThreshold="
                 + getAutoDataSwitchAvailabilityStabilityTimeThreshold());
+        pw.println("getAutoDataSwitchPerformanceStabilityTimeThreshold="
+                + getAutoDataSwitchPerformanceStabilityTimeThreshold());
         pw.println("getAutoDataSwitchValidationMaxRetry=" + getAutoDataSwitchValidationMaxRetry());
         pw.decreaseIndent();
         pw.println("Metered APN types=" + mMeteredApnTypes.stream()
@@ -1434,7 +1500,8 @@
         pw.println("Capabilities exempt from single PDN=" + mCapabilitiesExemptFromSingleDataList
                 .stream().map(DataUtils::networkCapabilityToString)
                 .collect(Collectors.joining(",")));
-        pw.println("mShouldKeepNetworkUpInNoVops=" + mShouldKeepNetworkUpInNonVops);
+        pw.println("mShouldKeepNetworkUpInNonVops=" + mShouldKeepNetworkUpInNonVops);
+        pw.println("mEnabledVopsNetworkTypesInNonVops=" + mEnabledVopsNetworkTypesInNonVops);
         pw.println("isPingTestBeforeAutoDataSwitchRequired="
                 + isPingTestBeforeAutoDataSwitchRequired());
         pw.println("Unmetered network types=" + String.join(",", mUnmeteredNetworkTypes));
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index 2ba3fe4..3d10e9c 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -155,6 +155,21 @@
     }
 
     /**
+     * Check if all the disallowed reasons are a subset of the given reason.
+     *
+     * @param reasons The given reason to check
+     * @return {@code true} if it doesn't contain any disallowed reasons other than the given
+     * reasons.
+     */
+    public boolean isSubsetOf(DataDisallowedReason... reasons) {
+        int matched = 0;
+        for (DataDisallowedReason requestedReason : reasons) {
+            if (mDataDisallowedReasons.contains(requestedReason)) matched++;
+        }
+        return matched == mDataDisallowedReasons.size();
+    }
+
+    /**
      * Check if the any of the disallowed reasons match one of the provided reason.
      *
      * @param reasons The given reasons to check.
@@ -242,7 +257,9 @@
         /** Tracking area code changed. */
         TAC_CHANGED(true),
         /** Unsatisfied network request detached. */
-        UNSATISFIED_REQUEST_DETACHED(true);
+        UNSATISFIED_REQUEST_DETACHED(true),
+        /** track bootstrap sim data usage */
+        CHECK_DATA_USAGE(false);
 
         /**
          * {@code true} if the evaluation is due to environmental changes (i.e. SIM removal,
@@ -290,6 +307,8 @@
         SIM_NOT_READY(true),
         /** Concurrent voice and data is not allowed. */
         CONCURRENT_VOICE_DATA_NOT_ALLOWED(true),
+        /** Service option not supported. */
+        SERVICE_OPTION_NOT_SUPPORTED(true),
         /** Carrier notified data should be restricted. */
         DATA_RESTRICTED_BY_NETWORK(true),
         /** Radio power is off (i.e. airplane mode on) */
@@ -325,7 +344,9 @@
         /** Data enabled settings are not ready. */
         DATA_SETTINGS_NOT_READY(true),
         /** Handover max retry stopped but network is not on the preferred transport. */
-        HANDOVER_RETRY_STOPPED(true);
+        HANDOVER_RETRY_STOPPED(true),
+        /** BootStrap sim data limit reached. */
+        DATA_LIMIT_REACHED(true);
 
         private final boolean mIsHardReason;
 
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 6ba251b..c1e61c6 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -31,6 +31,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
 import android.net.NetworkProvider;
+import android.net.NetworkRequest;
 import android.net.NetworkScore;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
@@ -74,6 +75,7 @@
 import android.telephony.data.DataService;
 import android.telephony.data.DataServiceCallback;
 import android.telephony.data.NetworkSliceInfo;
+import android.telephony.data.Qos;
 import android.telephony.data.QosBearerSession;
 import android.telephony.data.TrafficDescriptor;
 import android.telephony.data.TrafficDescriptor.OsAppId;
@@ -90,16 +92,20 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RIL;
+import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataEvaluation.DataAllowedReason;
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
 import com.android.internal.telephony.data.DataRetryManager.DataHandoverRetryEntry;
 import com.android.internal.telephony.data.DataRetryManager.DataRetryEntry;
+import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
 import com.android.internal.telephony.data.TelephonyNetworkAgent.TelephonyNetworkAgentCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -116,6 +122,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -253,6 +260,12 @@
      */
     private static final int EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE = 27;
 
+    /** Event for data network validation request from the AccessNetworksManager. */
+    private static final int EVENT_DATA_NETWORK_VALIDATION_REQUESTED = 28;
+
+    /** Event for response to data network validation request. */
+    private static final int EVENT_DATA_NETWORK_VALIDATION_RESPONSE = 29;
+
     /** Invalid context id. */
     private static final int INVALID_CID = -1;
 
@@ -278,6 +291,7 @@
                     TEAR_DOWN_REASON_RAT_NOT_ALLOWED,
                     TEAR_DOWN_REASON_ROAMING_DISABLED,
                     TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED,
+                    TEAR_DOWN_REASON_SERVICE_OPTION_NOT_SUPPORTED,
                     TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY,
                     TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER,
                     TEAR_DOWN_REASON_DATA_STALL,
@@ -299,6 +313,7 @@
                     TEAR_DOWN_REASON_ILLEGAL_STATE,
                     TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK,
                     TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED,
+                    TEAR_DOWN_REASON_DATA_LIMIT_REACHED,
             })
     public @interface TearDownReason {}
 
@@ -329,7 +344,8 @@
     /** Data network tear down due to concurrent voice/data not allowed. */
     public static final int TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED = 8;
 
-
+    /** Data network tear down due to service option is not supported. */
+    public static final int TEAR_DOWN_REASON_SERVICE_OPTION_NOT_SUPPORTED = 9;
 
     /** Data network tear down due to data service unbound. */
     public static final int TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY = 10;
@@ -394,6 +410,13 @@
     /** Data network tear down due to preferred data switched to another phone. */
     public static final int TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED = 30;
 
+    /** Data network tear down due to bootstrap sim data limit reached. */
+    public static final int TEAR_DOWN_REASON_DATA_LIMIT_REACHED = 31;
+
+    //********************************************************************************************//
+    // WHENEVER ADD A NEW TEAR DOWN REASON, PLEASE UPDATE DataDeactivateReasonEnum in enums.proto //
+    //********************************************************************************************//
+
     @IntDef(prefix = {"BANDWIDTH_SOURCE_"},
             value = {
                     BANDWIDTH_SOURCE_UNKNOWN,
@@ -436,9 +459,9 @@
             // Connectivity service will support NOT_METERED as a mutable and requestable
             // capability.
             NetworkCapabilities.NET_CAPABILITY_NOT_METERED,
-            // Even though MMTEL is an immutable capability, we still make it an mutable capability
-            // here before we have a better solution to deal with network transition from VoPS
-            // to non-VoPS network.
+            // Dynamically add and remove MMTEL capability when network transition between VoPS
+            // and non-VoPS network if the request is not MMTEL. For MMTEL, we retain the capability
+            // to prevent immediate tear down.
             NetworkCapabilities.NET_CAPABILITY_MMTEL
     );
 
@@ -486,6 +509,9 @@
     /** The phone instance. */
     private final @NonNull Phone mPhone;
 
+    /** Feature flags */
+    private final @NonNull FeatureFlags mFlags;
+
     /**
      * The subscription id. This is assigned when the network is created, and not supposed to
      * change afterwards.
@@ -493,7 +519,7 @@
     private final int mSubId;
 
     /** The network score of this network. */
-    private int mNetworkScore;
+    private @NonNull NetworkScore mNetworkScore;
 
     /**
      * Indicates that
@@ -556,6 +582,9 @@
     private final @NonNull DataNetworkController.DataNetworkControllerCallback
             mDataNetworkControllerCallback;
 
+    /** Data settings manager callback. */
+    private @NonNull DataSettingsManagerCallback mDataSettingsManagerCallback;
+
     /** Data config manager. */
     private final @NonNull DataConfigManager mDataConfigManager;
 
@@ -681,6 +710,9 @@
     /** The QOS bearer sessions. */
     private final @NonNull List<QosBearerSession> mQosBearerSessions = new ArrayList<>();
 
+    /** The QOS for the Default Bearer, should be non-null on LTE and NR */
+    private @Nullable Qos mDefaultQos;
+
     /**
      * The UIDs of packages that have carrier privilege.
      */
@@ -705,6 +737,24 @@
     private @Nullable DataConfigManagerCallback mDataConfigManagerCallback;
 
     /**
+     * Network validation status for this data network. If the data service provider does not
+     * support the network validation feature, should be UNSUPPORTED.
+     */
+    private @PreciseDataConnectionState.NetworkValidationStatus int mNetworkValidationStatus =
+            PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED;
+
+    /**
+     * Callback used to respond to a network validation request to determine whether the request is
+     * successfully submitted. If the request has been submitted, change it to null.
+     */
+    private @Nullable Consumer<Integer> mNetworkValidationResultCodeCallback;
+
+    /**
+     * Callback used to listen QNS preference changes.
+     */
+    private @Nullable AccessNetworksManagerCallback mAccessNetworksManagerCallback;
+
+    /**
      * The network bandwidth.
      */
     public static class NetworkBandwidth {
@@ -872,6 +922,14 @@
          */
         public abstract void onRetryUnsatisfiedNetworkRequest(
                 @NonNull TelephonyNetworkRequest networkRequest);
+
+        /**
+         * Called when QosBearerSessions bearer changed, which indicates VoNr or VoLte calls.
+         *
+         * @param qosBearerSessions The current qosBearerSessions.
+         */
+        public abstract void onQosSessionsChanged(
+                @NonNull List<QosBearerSession> qosBearerSessions);
     }
 
     /**
@@ -887,7 +945,7 @@
      * @param dataAllowedReason The reason that why setting up this data network is allowed.
      * @param callback The callback to receives data network state update.
      */
-    public DataNetwork(@NonNull Phone phone, @NonNull Looper looper,
+    public DataNetwork(@NonNull Phone phone, FeatureFlags featureFlags, @NonNull Looper looper,
             @NonNull SparseArray<DataServiceManager> dataServiceManagers,
             @NonNull DataProfile dataProfile,
             @NonNull NetworkRequestList networkRequestList,
@@ -901,6 +959,7 @@
         initializeStateMachine();
 
         mPhone = phone;
+        mFlags = featureFlags;
         mSubId = phone.getSubId();
         mRil = mPhone.mCi;
         mLinkProperties = new LinkProperties();
@@ -1024,10 +1083,18 @@
                 mPhone.getPhoneId());
         final NetworkProvider provider = (null == factory) ? null : factory.getProvider();
 
-        mNetworkScore = getNetworkScore();
+        // Always prefer IWLAN network for MMS designated network.
+        // TODO(b/293656884) Proper use of primary transport to avoid conflicting with DSDA.
+        boolean isPreferred = mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+                && getApnTypeNetworkCapability() == NetworkCapabilities.NET_CAPABILITY_MMS;
+
+        mNetworkScore = new NetworkScore.Builder().setTransportPrimary(isPreferred)
+                .setKeepConnectedReason(isHandoverInProgress()
+                        ? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER
+                        : NetworkScore.KEEP_CONNECTED_NONE).build();
+
         return new TelephonyNetworkAgent(mPhone, getHandler().getLooper(), this,
-                new NetworkScore.Builder().setLegacyInt(mNetworkScore).build(),
-                configBuilder.build(), provider,
+                mNetworkScore, configBuilder.build(), provider,
                 new TelephonyNetworkAgentCallback(getHandler()::post) {
                     @Override
                     public void onValidationStatus(@ValidationStatus int status,
@@ -1058,6 +1125,34 @@
             mRil.registerForPcoData(getHandler(), EVENT_PCO_DATA_RECEIVED, null);
 
             mDataConfigManager.registerCallback(mDataConfigManagerCallback);
+
+            mDataSettingsManagerCallback = new DataSettingsManagerCallback(getHandler()::post) {
+                @Override
+                public void onDataEnabledChanged(boolean enabled,
+                        @TelephonyManager.DataEnabledChangedReason int reason,
+                        @NonNull String callingPackage) {
+                    if (enabled) {
+                        // The NOT_RESTRICTED capability might be changed after data enabled. We
+                        // need to update the capabilities again.
+                        log("Data enabled. update network capabilities.");
+                        updateNetworkCapabilities();
+                    }
+                }
+
+                @Override
+                public void onDataRoamingEnabledChanged(boolean enabled) {
+                    if (enabled) {
+                        // The NOT_RESTRICTED capability might be changed after data roaming
+                        // enabled. We need to update the capabilities again.
+                        log("Data roaming enabled. update network capabilities.");
+                        updateNetworkCapabilities();
+                    }
+                }
+            };
+
+            mDataNetworkController.getDataSettingsManager()
+                    .registerCallback(mDataSettingsManagerCallback);
+
             mPhone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
                     getHandler(), EVENT_DISPLAY_INFO_CHANGED, null);
             mPhone.getServiceStateTracker().registerForServiceStateChanged(getHandler(),
@@ -1097,6 +1192,23 @@
                         getHandler(), EVENT_VOICE_CALL_ENDED, null);
             }
 
+            if (mFlags.forceIwlanMms()) {
+                if (mDataProfile.canSatisfy(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+                    mAccessNetworksManagerCallback = new AccessNetworksManagerCallback(
+                            getHandler()::post) {
+                        @Override
+                        public void onPreferredTransportChanged(
+                                @NetCapability int networkCapability) {
+                            if (networkCapability == NetworkCapabilities.NET_CAPABILITY_MMS) {
+                                log("MMS preference changed.");
+                                updateNetworkCapabilities();
+                            }
+                        }
+                    };
+                    mAccessNetworksManager.registerCallback(mAccessNetworksManagerCallback);
+                }
+            }
+
             // Only add symmetric code here, for example, registering and unregistering.
             // DefaultState.enter() is the starting point in the life cycle of the DataNetwork,
             // and DefaultState.exit() is the end. For non-symmetric initializing works, put them
@@ -1106,6 +1218,10 @@
         @Override
         public void exit() {
             logv("Unregistering all events.");
+            if (mFlags.forceIwlanMms() && mAccessNetworksManagerCallback != null) {
+                mAccessNetworksManager.unregisterCallback(mAccessNetworksManagerCallback);
+            }
+
             // Check null for devices not supporting FEATURE_TELEPHONY_IMS.
             if (mPhone.getImsPhone() != null) {
                 mPhone.getImsPhone().getCallTracker().unregisterForVoiceCallStarted(getHandler());
@@ -1126,6 +1242,8 @@
             mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
             mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(
                     getHandler());
+            mDataNetworkController.getDataSettingsManager()
+                    .unregisterCallback(mDataSettingsManagerCallback);
             mRil.unregisterForPcoData(getHandler());
             mDataConfigManager.unregisterCallback(mDataConfigManagerCallback);
         }
@@ -1157,13 +1275,13 @@
                 }
                 case EVENT_ATTACH_NETWORK_REQUEST: {
                     onAttachNetworkRequests((NetworkRequestList) msg.obj);
-                    updateNetworkScore();
+                    updateNetworkScore(isHandoverInProgress());
                     break;
                 }
                 case EVENT_DETACH_NETWORK_REQUEST: {
                     onDetachNetworkRequest((TelephonyNetworkRequest) msg.obj,
                             msg.arg1 != 0 /* shouldRetry */);
-                    updateNetworkScore();
+                    updateNetworkScore(isHandoverInProgress());
                     break;
                 }
                 case EVENT_DETACH_ALL_NETWORK_REQUESTS: {
@@ -1224,6 +1342,14 @@
                     loge(eventToString(msg.what) + ": transition to disconnected state");
                     transitionTo(mDisconnectedState);
                     break;
+                case EVENT_DATA_NETWORK_VALIDATION_REQUESTED:
+                    // If the data network is not connected, the request should be ignored.
+                    handleErrorDataNetworkValidationRequest((Consumer<Integer>) msg.obj);
+                    break;
+                case EVENT_DATA_NETWORK_VALIDATION_RESPONSE:
+                    // handle the resultCode in response for the request.
+                    handleDataNetworkValidationRequestResultCode(msg.arg1 /* resultCode */);
+                    break;
                 default:
                     loge("Unhandled event " + eventToString(msg.what));
                     break;
@@ -1238,6 +1364,8 @@
      * @see DataNetwork for the state machine diagram.
      */
     private final class ConnectingState extends State {
+        /** Used for checking setup response IP mismatch. */
+        @NetworkRegistrationInfo.RegistrationState private int mRegStateWhenSetup;
         @Override
         public void enter() {
             sendMessageDelayed(EVENT_STUCK_IN_TRANSIENT_STATE,
@@ -1305,11 +1433,149 @@
                     mFailCause = DataFailCause.NO_RETRY_FAILURE;
                     transitionTo(mDisconnectedState);
                     break;
+                case EVENT_DEACTIVATE_DATA_NETWORK_RESPONSE:
+                    int responseCode = msg.arg1;
+                    onDeactivateResponse(responseCode);
+                    break;
                 default:
                     return NOT_HANDLED;
             }
             return HANDLED;
         }
+
+        /**
+         * Setup a data network.
+         */
+        private void setupData() {
+            int dataNetworkType = getDataNetworkType();
+
+            NetworkRegistrationInfo nri = getNetworkRegistrationInfo();
+            mRegStateWhenSetup = nri != null
+                    ? nri.getNetworkRegistrationState()
+                    : NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
+            // We need to use the actual modem roaming state instead of the framework roaming state
+            // here. This flag is only passed down to ril_service for picking the correct protocol
+            // (for old modem backward compatibility).
+            boolean isModemRoaming = mPhone.getServiceState().getDataRoamingFromRegistration();
+
+            // Set this flag to true if the user turns on data roaming. Or if we override the
+            // roaming state in framework, we should set this flag to true as well so the modem will
+            // not reject the data call setup (because the modem thinks the device is roaming).
+            boolean allowRoaming = mPhone.getDataRoamingEnabled()
+                    || (isModemRoaming && (!mPhone.getServiceState().getDataRoaming()
+                    /*|| isUnmeteredUseOnly()*/));
+
+            TrafficDescriptor trafficDescriptor = mDataProfile.getTrafficDescriptor();
+            final boolean matchAllRuleAllowed = trafficDescriptor == null
+                    || !TextUtils.isEmpty(trafficDescriptor.getDataNetworkName())
+                    // Both OsAppId and APN name are null. This helps for modem to handle when we
+                    // are on 5G or LTE with URSP support in falling back to default network.
+                    || (TextUtils.isEmpty(trafficDescriptor.getDataNetworkName())
+                    && trafficDescriptor.getOsAppId() == null);
+
+            int accessNetwork = DataUtils.networkTypeToAccessNetworkType(dataNetworkType);
+
+            mDataServiceManagers.get(mTransport)
+                    .setupDataCall(accessNetwork, mDataProfile, isModemRoaming, allowRoaming,
+                            DataService.REQUEST_REASON_NORMAL, null, mPduSessionId, null,
+                            trafficDescriptor, matchAllRuleAllowed,
+                            obtainMessage(EVENT_SETUP_DATA_NETWORK_RESPONSE));
+
+            int apnTypeBitmask = mDataProfile.getApnSetting() != null
+                    ? mDataProfile.getApnSetting().getApnTypeBitmask() : ApnSetting.TYPE_NONE;
+            mDataCallSessionStats.onSetupDataCall(apnTypeBitmask);
+
+            logl("setupData: accessNetwork="
+                    + AccessNetworkType.toString(accessNetwork) + ", " + mDataProfile
+                    + ", isModemRoaming=" + isModemRoaming + ", allowRoaming=" + allowRoaming
+                    + ", PDU session id=" + mPduSessionId + ", matchAllRuleAllowed="
+                    + matchAllRuleAllowed);
+            TelephonyMetrics.getInstance().writeSetupDataCall(mPhone.getPhoneId(),
+                    ServiceState.networkTypeToRilRadioTechnology(dataNetworkType),
+                    mDataProfile.getProfileId(), mDataProfile.getApn(),
+                    mDataProfile.getProtocolType());
+        }
+
+        /**
+         * Called when receiving setup data network response from the data service.
+         *
+         * @param resultCode The result code.
+         * @param response The response.
+         */
+        private void onSetupResponse(@DataServiceCallback.ResultCode int resultCode,
+                @Nullable DataCallResponse response) {
+            logl("onSetupResponse: resultCode=" + DataServiceCallback.resultCodeToString(resultCode)
+                    + ", response=" + response);
+            mFailCause = getFailCauseFromDataCallResponse(resultCode, response);
+            validateDataCallResponse(response, mRegStateWhenSetup);
+            if (mFailCause == DataFailCause.NONE) {
+                DataNetwork dataNetwork = mDataNetworkController.getDataNetworkByInterface(
+                        response.getInterfaceName());
+                if (dataNetwork != null) {
+                    logl("Interface " + response.getInterfaceName() + " has been already used by "
+                            + dataNetwork + ". Silently tear down now.");
+                    // If this is a pre-5G data setup, that means APN database has some problems.
+                    // For example, different APN settings have the same APN name.
+                    if (response.getTrafficDescriptors().isEmpty() && dataNetwork.isConnected()) {
+                        reportAnomaly("Duplicate network interface " + response.getInterfaceName()
+                                + " detected.", "62f66e7e-8d71-45de-a57b-dc5c78223fd5");
+                    }
+
+                    // Do not actually invoke onTearDown, otherwise the existing data network will
+                    // be torn down.
+                    mRetryDelayMillis = DataCallResponse.RETRY_DURATION_UNDEFINED;
+                    mFailCause = DataFailCause.NO_RETRY_FAILURE;
+                    transitionTo(mDisconnectedState);
+                    return;
+                }
+
+                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.size() == 0) {
+                    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
+                    // state, which will never happen in this case.
+                    onTearDown(TEAR_DOWN_REASON_NO_LIVE_REQUEST);
+                    return;
+                }
+
+                if (mVcnManager != null && mVcnManager.applyVcnNetworkPolicy(mNetworkCapabilities,
+                        mLinkProperties).isTeardownRequested()) {
+                    log("VCN service requested to tear down the network.");
+                    // Directly call onTearDown here. Calling tearDown will cause deadlock because
+                    // EVENT_TEAR_DOWN_NETWORK is deferred until state machine enters connected
+                    // state, which will never happen in this case.
+                    onTearDown(TEAR_DOWN_REASON_VCN_REQUESTED);
+                    return;
+                }
+
+                transitionTo(mConnectedState);
+            } else {
+                // Setup data failed.
+                mRetryDelayMillis = response != null ? response.getRetryDurationMillis()
+                        : DataCallResponse.RETRY_DURATION_UNDEFINED;
+                transitionTo(mDisconnectedState);
+            }
+
+            int apnTypeBitmask = ApnSetting.TYPE_NONE;
+            int protocol = ApnSetting.PROTOCOL_UNKNOWN;
+            if (mDataProfile.getApnSetting() != null) {
+                apnTypeBitmask = mDataProfile.getApnSetting().getApnTypeBitmask();
+                protocol = mDataProfile.getApnSetting().getProtocol();
+            }
+            mDataCallSessionStats.onSetupDataCallResponse(response,
+                    getDataNetworkType(),
+                    apnTypeBitmask,
+                    protocol,
+                    // Log the raw fail cause to avoid large amount of UNKNOWN showing on metrics.
+                    response != null ? response.getCause() : mFailCause);
+        }
     }
 
     /**
@@ -1429,6 +1695,10 @@
                     updateSuspendState();
                     updateNetworkCapabilities();
                     break;
+                case EVENT_DATA_NETWORK_VALIDATION_REQUESTED:
+                    // Network validation request can be accepted if the data is in connected state
+                    handleDataNetworkValidationRequest((Consumer<Integer>) msg.obj);
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -1447,11 +1717,13 @@
             sendMessageDelayed(EVENT_STUCK_IN_TRANSIENT_STATE,
                     mDataConfigManager.getNetworkHandoverTimeoutMs());
             notifyPreciseDataConnectionState();
+            updateNetworkScore(true /* keepConnectedForHandover */);
         }
 
         @Override
         public void exit() {
             removeMessages(EVENT_STUCK_IN_TRANSIENT_STATE);
+            updateNetworkScore(false /* keepConnectedForHandover */);
         }
 
         @Override
@@ -1481,6 +1753,7 @@
                 case EVENT_CSS_INDICATOR_CHANGED:
                 case EVENT_VOICE_CALL_ENDED:
                 case EVENT_VOICE_CALL_STARTED:
+                case EVENT_DATA_NETWORK_VALIDATION_REQUESTED:
                     // Defer the request until handover succeeds or fails.
                     log("Defer message " + eventToString(msg.what));
                     deferMessage(msg);
@@ -1615,6 +1888,8 @@
 
             if (mEverConnected) {
                 mLinkStatus = DataCallResponse.LINK_STATUS_INACTIVE;
+                mNetworkValidationStatus =
+                        PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED;
                 mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
                         .onLinkStatusChanged(DataNetwork.this, mLinkStatus));
                 mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
@@ -1951,30 +2226,34 @@
             }
         }
 
-        // Once we set the MMTEL capability, we should never remove it because it's an immutable
+        // If MMTEL capability is requested, we should not remove it because it's an immutable
         // capability defined by connectivity service. When the device enters from VoPS to non-VoPS,
         // we should perform grace tear down from data network controller if needed.
-        if (mNetworkCapabilities != null
-                && mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)) {
-            // Previous capability has MMTEL, so add it again.
+        if (hasNetworkCapabilityInNetworkRequests(NetworkCapabilities.NET_CAPABILITY_MMTEL)) {
+            // Request has MMTEL, add it again so the network won't be unwanted by connectivity.
             builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
-        } else {
+        } else if (mDataProfile.canSatisfy(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+            // Request has IMS capability only.
             // Always add MMTEL capability on IMS network unless network explicitly indicates VoPS
             // not supported.
-            if (mDataProfile.canSatisfy(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-                builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
-                if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-                    NetworkRegistrationInfo nri = getNetworkRegistrationInfo();
-                    if (nri != null) {
-                        DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
-                        // Check if the network is non-VoPS.
-                        if (dsri != null && dsri.getVopsSupportInfo() != null
-                                && !dsri.getVopsSupportInfo().isVopsSupported()
-                                && !mDataConfigManager.shouldKeepNetworkUpInNonVops()) {
-                            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
-                        }
-                        log("updateNetworkCapabilities: dsri=" + dsri);
+            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
+            if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+                NetworkRegistrationInfo nri = getNetworkRegistrationInfo();
+                if (nri != null) {
+                    DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
+                    // Check if the network is non-VoPS.
+                    if (dsri != null && dsri.getVopsSupportInfo() != null
+                            && !dsri.getVopsSupportInfo().isVopsSupported()
+                            // Reflect the actual MMTEL if flag on.
+                            && (mFlags.allowMmtelInNonVops()
+                            // Deceive Connectivity service to satisfy an MMTEL request, this should
+                            // be useless because we reach here if no MMTEL request, then removing
+                            // MMTEL capability shouldn't have any impacts.
+                            || !mDataConfigManager.shouldKeepNetworkUpInNonVops(
+                                    nri.getNetworkRegistrationState()))) {
+                        builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
                     }
+                    log("updateNetworkCapabilities: dsri=" + dsri);
                 }
             }
         }
@@ -2091,6 +2370,39 @@
             }
         }
 
+        if (mDataNetworkController.isEsimBootStrapProvisioningActivated()) {
+            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        }
+
+        // Check if the feature force MMS on IWLAN is enabled. When the feature is enabled, MMS
+        // will be attempted on IWLAN if possible, even if existing cellular networks already
+        // supports IWLAN.
+        if (mFlags.forceIwlanMms() && builder.build()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+            // If QNS sets MMS preferred on IWLAN, and it is possible to setup an MMS network on
+            // IWLAN, then we need to remove the MMS capability on the cellular network. This will
+            // allow the new MMS network to be brought up on IWLAN when MMS network request arrives.
+            if (mAccessNetworksManager.getPreferredTransportByNetworkCapability(
+                    NetworkCapabilities.NET_CAPABILITY_MMS)
+                    == AccessNetworkConstants.TRANSPORT_TYPE_WLAN && mTransport
+                    == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+
+                DataProfile dataProfile = mDataNetworkController.getDataProfileManager()
+                        .getDataProfileForNetworkRequest(new TelephonyNetworkRequest(
+                                new NetworkRequest.Builder().addCapability(
+                                NetworkCapabilities.NET_CAPABILITY_MMS).build(), mPhone),
+                        TelephonyManager.NETWORK_TYPE_IWLAN, false, false, false);
+                // If we find another data data profile that can support MMS on IWLAN, then remove
+                // the MMS capability from this cellular network. This will allow IWLAN to be
+                // brought up for MMS later.
+                if (dataProfile != null && !dataProfile.equals(mDataProfile)) {
+                    log("Found a different data profile " + mDataProfile.getApn()
+                            + " that can serve MMS on IWLAN.");
+                    builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+                }
+            }
+        }
+
         // If one of the capabilities are for special use, for example, IMS, CBS, then this
         // network should be restricted, regardless data is enabled or not.
         if (NetworkCapabilitiesUtils.inferRestrictedCapability(builder.build())
@@ -2220,54 +2532,6 @@
     }
 
     /**
-     * Setup a data network.
-     */
-    private void setupData() {
-        int dataNetworkType = getDataNetworkType();
-
-        // We need to use the actual modem roaming state instead of the framework roaming state
-        // here. This flag is only passed down to ril_service for picking the correct protocol (for
-        // old modem backward compatibility).
-        boolean isModemRoaming = mPhone.getServiceState().getDataRoamingFromRegistration();
-
-        // Set this flag to true if the user turns on data roaming. Or if we override the roaming
-        // state in framework, we should set this flag to true as well so the modem will not reject
-        // the data call setup (because the modem actually thinks the device is roaming).
-        boolean allowRoaming = mPhone.getDataRoamingEnabled()
-                || (isModemRoaming && (!mPhone.getServiceState().getDataRoaming()
-                /*|| isUnmeteredUseOnly()*/));
-
-        TrafficDescriptor trafficDescriptor = mDataProfile.getTrafficDescriptor();
-        final boolean matchAllRuleAllowed = trafficDescriptor == null
-                || !TextUtils.isEmpty(trafficDescriptor.getDataNetworkName())
-                // Both OsAppId and APN name are null. This helps for modem to handle when we
-                // are on 5G or LTE with URSP support in falling back to default network.
-                || (TextUtils.isEmpty(trafficDescriptor.getDataNetworkName())
-                && trafficDescriptor.getOsAppId() == null);
-
-        int accessNetwork = DataUtils.networkTypeToAccessNetworkType(dataNetworkType);
-
-        mDataServiceManagers.get(mTransport)
-                .setupDataCall(accessNetwork, mDataProfile, isModemRoaming, allowRoaming,
-                        DataService.REQUEST_REASON_NORMAL, null, mPduSessionId, null,
-                        trafficDescriptor, matchAllRuleAllowed,
-                        obtainMessage(EVENT_SETUP_DATA_NETWORK_RESPONSE));
-
-        int apnTypeBitmask = mDataProfile.getApnSetting() != null
-                ? mDataProfile.getApnSetting().getApnTypeBitmask() : ApnSetting.TYPE_NONE;
-        mDataCallSessionStats.onSetupDataCall(apnTypeBitmask);
-
-        logl("setupData: accessNetwork="
-                + AccessNetworkType.toString(accessNetwork) + ", " + mDataProfile
-                + ", isModemRoaming=" + isModemRoaming + ", allowRoaming=" + allowRoaming
-                + ", PDU session id=" + mPduSessionId + ", matchAllRuleAllowed="
-                + matchAllRuleAllowed);
-        TelephonyMetrics.getInstance().writeSetupDataCall(mPhone.getPhoneId(),
-                ServiceState.networkTypeToRilRadioTechnology(dataNetworkType),
-                mDataProfile.getProfileId(), mDataProfile.getApn(), mDataProfile.getProtocolType());
-    }
-
-    /**
      * Get fail cause from {@link DataCallResponse} and the result code.
      *
      * @param resultCode The result code returned from
@@ -2419,6 +2683,14 @@
         mTrafficDescriptors.clear();
         mTrafficDescriptors.addAll(response.getTrafficDescriptors());
 
+        mDefaultQos = response.getDefaultQos();
+
+
+        Set<QosBearerSession> newSessions = new HashSet<>(response.getQosBearerSessions());
+        if (newSessions.size() != mQosBearerSessions.size()
+                || !newSessions.containsAll(mQosBearerSessions)) {
+            mDataNetworkCallback.onQosSessionsChanged(response.getQosBearerSessions());
+        }
         mQosBearerSessions.clear();
         mQosBearerSessions.addAll(response.getQosBearerSessions());
         if (mQosCallbackTracker != null) {
@@ -2444,96 +2716,17 @@
         }
 
         updateNetworkCapabilities();
-    }
-
-    /**
-     * Called when receiving setup data network response from the data service.
-     *
-     * @param resultCode The result code.
-     * @param response The response.
-     */
-    private void onSetupResponse(@DataServiceCallback.ResultCode int resultCode,
-            @Nullable DataCallResponse response) {
-        logl("onSetupResponse: resultCode=" + DataServiceCallback.resultCodeToString(resultCode)
-                + ", response=" + response);
-        mFailCause = getFailCauseFromDataCallResponse(resultCode, response);
-        validateDataCallResponse(response, true /*isSetupResponse*/);
-        if (mFailCause == DataFailCause.NONE) {
-            DataNetwork dataNetwork = mDataNetworkController.getDataNetworkByInterface(
-                    response.getInterfaceName());
-            if (dataNetwork != null) {
-                logl("Interface " + response.getInterfaceName() + " has been already used by "
-                        + dataNetwork + ". Silently tear down now.");
-                // If this is a pre-5G data setup, that means APN database has some problems. For
-                // example, different APN settings have the same APN name.
-                if (response.getTrafficDescriptors().isEmpty() && dataNetwork.isConnected()) {
-                    reportAnomaly("Duplicate network interface " + response.getInterfaceName()
-                            + " detected.", "62f66e7e-8d71-45de-a57b-dc5c78223fd5");
-                }
-
-                // Do not actually invoke onTearDown, otherwise the existing data network will be
-                // torn down.
-                mRetryDelayMillis = DataCallResponse.RETRY_DURATION_UNDEFINED;
-                mFailCause = DataFailCause.NO_RETRY_FAILURE;
-                transitionTo(mDisconnectedState);
-                return;
-            }
-
-            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.size() == 0) {
-                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 state,
-                // which will never happen in this case.
-                onTearDown(TEAR_DOWN_REASON_NO_LIVE_REQUEST);
-                return;
-            }
-
-            if (mVcnManager != null && mVcnManager.applyVcnNetworkPolicy(mNetworkCapabilities,
-                    mLinkProperties).isTeardownRequested()) {
-                log("VCN service requested to tear down the network.");
-                // Directly call onTearDown here. Calling tearDown will cause deadlock because
-                // EVENT_TEAR_DOWN_NETWORK is deferred until state machine enters connected state,
-                // which will never happen in this case.
-                onTearDown(TEAR_DOWN_REASON_VCN_REQUESTED);
-                return;
-            }
-
-            transitionTo(mConnectedState);
-        } else {
-            // Setup data failed.
-            mRetryDelayMillis = response != null ? response.getRetryDurationMillis()
-                    : DataCallResponse.RETRY_DURATION_UNDEFINED;
-            transitionTo(mDisconnectedState);
-        }
-
-        int apnTypeBitmask = ApnSetting.TYPE_NONE;
-        int protocol = ApnSetting.PROTOCOL_UNKNOWN;
-        if (mDataProfile.getApnSetting() != null) {
-            apnTypeBitmask = mDataProfile.getApnSetting().getApnTypeBitmask();
-            protocol = mDataProfile.getApnSetting().getProtocol();
-        }
-        mDataCallSessionStats.onSetupDataCallResponse(response,
-                getDataNetworkType(),
-                apnTypeBitmask,
-                protocol,
-                // Log the raw fail cause to avoid large amount of UNKNOWN showing on metrics.
-                response != null ? response.getCause() : mFailCause);
+        updateValidationStatus(response.getNetworkValidationStatus());
     }
 
     /**
      * If the {@link DataCallResponse} contains invalid info, triggers an anomaly report.
      *
      * @param response The response to be validated
-     * @param isSetupResponse {@code true} if the response is for initial data call setup
+     * @param setupRegState Registration state if the response is for initial data call setup.
      */
     private void validateDataCallResponse(@Nullable DataCallResponse response,
-            boolean isSetupResponse) {
+            @NetworkRegistrationInfo.RegistrationState int setupRegState) {
         if (response == null
                 || response.getLinkStatus() == DataCallResponse.LINK_STATUS_INACTIVE) return;
         int failCause = response.getCause();
@@ -2555,32 +2748,36 @@
             }
             // Check IP for initial setup response
             NetworkRegistrationInfo nri = getNetworkRegistrationInfo();
-            if (isSetupResponse
+            if (setupRegState != -1 // Is setup response
                     && mDataProfile.getApnSetting() != null && nri != null && nri.isInService()) {
+                boolean wasRoaming = setupRegState
+                        == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
                 boolean isRoaming = nri.getNetworkRegistrationState()
                         == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
-                int protocol = isRoaming ? mDataProfile.getApnSetting().getRoamingProtocol()
-                        : mDataProfile.getApnSetting().getProtocol();
-                String underlyingDataService = mTransport
-                        == 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);
-                        reportAnomaly(underlyingDataService + " reported mismatched IP "
-                                + "type. Requested IPv4 but got IPv6 address.",
-                                "7744f920-fb64-4db0-ba47-de0eae485a80");
-                    }
-                } 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);
-                        reportAnomaly(underlyingDataService + " reported mismatched IP "
-                                        + "type. Requested IPv6 but got IPv4 address.",
-                                "7744f920-fb64-4db0-ba47-de0eae485a80");
+                if (wasRoaming == isRoaming) { // Ignore check if in race condition.
+                    int protocol = isRoaming ? mDataProfile.getApnSetting().getRoamingProtocol()
+                            : mDataProfile.getApnSetting().getProtocol();
+                    String underlyingDataService = mTransport
+                            == 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);
+                            reportAnomaly(underlyingDataService + " reported mismatched IP "
+                                            + "type. Requested IPv4 but got IPv6 address.",
+                                    "7744f920-fb64-4db0-ba47-de0eae485a81");
+                        }
+                    } 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);
+                            reportAnomaly(underlyingDataService + " reported mismatched IP "
+                                            + "type. Requested IPv6 but got IPv4 address.",
+                                    "7744f920-fb64-4db0-ba47-de0eae485a81");
+                        }
                     }
                 }
             }
@@ -2653,7 +2850,7 @@
     public boolean shouldDelayImsTearDownDueToInCall() {
         return mDataConfigManager.isImsDelayTearDownUntilVoiceCallEndEnabled()
                 && mNetworkCapabilities != null
-                && mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
+                && mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                 && mPhone.getImsPhone() != null
                 && mPhone.getImsPhone().getCallTracker().getState()
                 != PhoneConstants.State.IDLE;
@@ -2717,7 +2914,7 @@
         if (response != null) {
             if (!response.equals(mDataCallResponse)) {
                 log("onDataStateChanged: " + response);
-                validateDataCallResponse(response, false /*isSetupResponse*/);
+                validateDataCallResponse(response, -1 /*setupRegState setup only*/);
                 mDataCallResponse = response;
                 if (response.getLinkStatus() != DataCallResponse.LINK_STATUS_INACTIVE) {
                     updateDataNetwork(response);
@@ -2978,39 +3175,23 @@
         return mLinkStatus;
     }
 
+
     /**
      * Update the network score and report to connectivity service if necessary.
+     *
+     * @param keepConnectedForHandover indicate handover is in progress or not.
      */
-    private void updateNetworkScore() {
-        int networkScore = getNetworkScore();
-        if (networkScore != mNetworkScore) {
-            logl("Updating score from " + mNetworkScore + " to " + networkScore);
-            mNetworkScore = networkScore;
+    private void updateNetworkScore(boolean keepConnectedForHandover) {
+        int connectedReason = keepConnectedForHandover
+                ? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER : NetworkScore.KEEP_CONNECTED_NONE;
+        if (mNetworkScore.getKeepConnectedReason() != connectedReason) {
+            mNetworkScore = new NetworkScore.Builder()
+                    .setKeepConnectedReason(connectedReason).build();
             mNetworkAgent.sendNetworkScore(mNetworkScore);
         }
     }
 
     /**
-     * @return The network score. The higher score of the network has higher chance to be
-     * selected by the connectivity service as active network.
-     */
-    private int getNetworkScore() {
-        // If it's serving a network request that asks NET_CAPABILITY_INTERNET and doesn't have
-        // specify a sub id, this data network is considered to be default internet data
-        // connection. In this case we assign a slightly higher score of 50. The intention is
-        // it will not be replaced by other data networks accidentally in DSDS use case.
-        int score = OTHER_NETWORK_SCORE;
-        for (TelephonyNetworkRequest networkRequest : mAttachedNetworkRequestList) {
-            if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                    && networkRequest.getNetworkSpecifier() == null) {
-                score = DEFAULT_INTERNET_NETWORK_SCORE;
-            }
-        }
-
-        return score;
-    }
-
-    /**
      * @return Network registration info on the current transport.
      */
     private @Nullable NetworkRegistrationInfo getNetworkRegistrationInfo() {
@@ -3154,13 +3335,7 @@
      * @return {@code true} if this data network supports internet.
      */
     public boolean isInternetSupported() {
-        return mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                && mNetworkCapabilities.hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                && mNetworkCapabilities.hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_TRUSTED)
-                && mNetworkCapabilities.hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+        return mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
     }
 
     /**
@@ -3185,6 +3360,8 @@
                 .setLinkProperties(mLinkProperties)
                 .setNetworkType(getDataNetworkType())
                 .setFailCause(mFailCause)
+                .setDefaultQos(mDefaultQos)
+                .setNetworkValidationStatus(mNetworkValidationStatus)
                 .build();
     }
 
@@ -3261,7 +3438,9 @@
                 && !mAttachedNetworkRequestList.isEmpty()) {
             TelephonyNetworkRequest networkRequest = mAttachedNetworkRequestList.get(0);
             DataProfile dataProfile = mDataNetworkController.getDataProfileManager()
-                    .getDataProfileForNetworkRequest(networkRequest, targetNetworkType, false);
+                    .getDataProfileForNetworkRequest(networkRequest, targetNetworkType,
+                            mPhone.getServiceState().isUsingNonTerrestrialNetwork(),
+                            mDataNetworkController.isEsimBootStrapProvisioningActivated(), false);
             // Some carriers have different profiles between cellular and IWLAN. We need to
             // dynamically switch profile, but only when those profiles have same APN name.
             if (dataProfile != null && dataProfile.getApnSetting() != null
@@ -3298,7 +3477,7 @@
         logl("onHandoverResponse: resultCode=" + DataServiceCallback.resultCodeToString(resultCode)
                 + ", response=" + response);
         mFailCause = getFailCauseFromDataCallResponse(resultCode, response);
-        validateDataCallResponse(response, false /*isSetupResponse*/);
+        validateDataCallResponse(response, -1 /*setupRegState setup only*/);
         if (mFailCause == DataFailCause.NONE) {
             // Handover succeeded.
 
@@ -3453,6 +3632,81 @@
     }
 
     /**
+     * The network validation requests moves to process on the statemachich handler. A request is
+     * processed according to state of the data network.
+     */
+    public void requestNetworkValidation(@NonNull Consumer<Integer> resultCodeCallback) {
+        // request a network validation by DataNetwork state
+        sendMessage(EVENT_DATA_NETWORK_VALIDATION_REQUESTED, resultCodeCallback);
+    }
+
+    /**
+     * Request network validation to data service provider.
+     */
+    private void handleDataNetworkValidationRequest(@NonNull Consumer<Integer> resultCodeCallback) {
+        if (mNetworkValidationResultCodeCallback != null) {
+            loge("requestNetworkValidation: previous networkValidationRequest is in progress.");
+            FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+                    .accept(DataServiceCallback.RESULT_ERROR_BUSY);
+            return;
+        }
+
+        mNetworkValidationResultCodeCallback = resultCodeCallback;
+
+        // Request validation directly from the data service.
+        mDataServiceManagers.get(mTransport).requestNetworkValidation(
+                mCid.get(mTransport), obtainMessage(EVENT_DATA_NETWORK_VALIDATION_RESPONSE));
+        log("handleDataNetworkValidationRequest, network validation requested");
+    }
+
+    private void handleErrorDataNetworkValidationRequest(
+            @NonNull Consumer<Integer> resultCodeCallback) {
+        loge("handleErrorDataNetworkValidationRequest: DataNetwork is not in Connected state");
+        FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+                .accept(DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+    }
+
+    /**
+     * handle the resultCode in response for the request.
+     *
+     * @param resultCode {@link DataServiceCallback.ResultCode}
+     */
+    private void handleDataNetworkValidationRequestResultCode(
+            @DataServiceCallback.ResultCode int resultCode) {
+        if (mNetworkValidationResultCodeCallback != null) {
+            log("handleDataNetworkValidationRequestResultCode, resultCode:"
+                    + DataServiceCallback.resultCodeToString(resultCode));
+            FunctionalUtils.ignoreRemoteException(mNetworkValidationResultCodeCallback::accept)
+                    .accept(resultCode);
+            mNetworkValidationResultCodeCallback = null;
+        }
+    }
+
+    /**
+     * Update the validation status from {@link DataCallResponse}, convert to network validation
+     * status {@link PreciseDataConnectionState.NetworkValidationStatus} and notify to
+     * {@link PreciseDataConnectionState} if status was changed.
+     *
+     * @param networkValidationStatus {@link PreciseDataConnectionState.NetworkValidationStatus}
+     */
+    private void updateValidationStatus(
+            @PreciseDataConnectionState.NetworkValidationStatus int networkValidationStatus) {
+        if (!mFlags.networkValidation()) {
+            // Do nothing, if network validation feature is disabled
+            return;
+        }
+
+        // if network validation status is changed, notify preciseDataConnectionState.
+        if (mNetworkValidationStatus != networkValidationStatus) {
+            log("updateValidationStatus:"
+                    + PreciseDataConnectionState.networkValidationStatusToString(
+                    networkValidationStatus));
+            mNetworkValidationStatus = networkValidationStatus;
+            notifyPreciseDataConnectionState();
+        }
+    }
+
+    /**
      * Convert the data tear down reason to string.
      *
      * @param reason Data deactivation reason.
@@ -3478,6 +3732,8 @@
                 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:
@@ -3520,6 +3776,8 @@
                 return "TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK";
             case TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED:
                 return "TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED";
+            case TEAR_DOWN_REASON_DATA_LIMIT_REACHED:
+                return "TEAR_DOWN_REASON_DATA_LIMIT_REACHED";
             default:
                 return "UNKNOWN(" + reason + ")";
         }
@@ -3583,6 +3841,10 @@
                 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 + ")";
         }
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index db78b8f..2fb23a7 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -20,6 +20,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -29,6 +31,7 @@
 import android.net.NetworkPolicyManager;
 import android.net.NetworkPolicyManager.SubscriptionCallback;
 import android.net.NetworkRequest;
+import android.net.NetworkTemplate;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -60,10 +63,13 @@
 import android.telephony.TelephonyManager.DataState;
 import android.telephony.TelephonyManager.SimState;
 import android.telephony.TelephonyRegistryManager;
+import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataCallResponse.HandoverFailureMode;
 import android.telephony.data.DataCallResponse.LinkStatus;
 import android.telephony.data.DataProfile;
+import android.telephony.data.DataServiceCallback;
+import android.telephony.data.QosBearerSession;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsManager;
 import android.telephony.ims.ImsReasonInfo;
@@ -99,8 +105,12 @@
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.DataStallRecoveryManager.DataStallRecoveryManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.ims.ImsResolver;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.internal.util.FunctionalUtils;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -112,6 +122,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -122,6 +133,7 @@
 import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -230,6 +242,18 @@
     private static final long REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_AFTER_DETACHED_DELAY_MILLIS =
             TimeUnit.SECONDS.toMillis(1);
 
+    /**
+     * 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);
+
+    /**
+     * bootstrap sim total data usage bytes
+     */
+    private long mBootStrapSimTotalDataUsageBytes = 0L;
+
     private final Phone mPhone;
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(128);
@@ -296,6 +320,9 @@
      */
     private @DataState int mInternetDataNetworkState = TelephonyManager.DATA_DISCONNECTED;
 
+    /** All the current connected/handover internet networks.  */
+    @NonNull private Set<DataNetwork> mConnectedInternetNetworks = new HashSet<>();
+
     /**
      * The IMS data network state. For now this is just for debugging purposes.
      */
@@ -379,6 +406,8 @@
     /** True after try to release an IMS network; False after try to request an IMS network. */
     private boolean mLastImsOperationIsRelease;
 
+    private final @NonNull FeatureFlags mFeatureFlags;
+
     /** The broadcast receiver. */
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -559,12 +588,13 @@
                 @ValidationStatus int validationStatus) {}
 
         /**
-         * Called when internet data network is connected.
+         * Called when a network that's capable of internet is newly connected or disconnected.
          *
          * @param internetNetworks The connected internet data network. It should be only one in
          *                         most of the cases.
          */
-        public void onInternetDataNetworkConnected(@NonNull List<DataNetwork> internetNetworks) {}
+        public void onConnectedInternetDataNetworksChanged(@NonNull Set<DataNetwork>
+                internetNetworks) {}
 
         /**
          * Called when data network is connected.
@@ -575,9 +605,6 @@
         public void onDataNetworkConnected(@TransportType int transport,
                 @NonNull DataProfile dataProfile) {}
 
-        /** Called when internet data network is disconnected. */
-        public void onInternetDataNetworkDisconnected() {}
-
         /**
          * Called when any data network existing status changed.
          *
@@ -614,6 +641,20 @@
          * @param transport The transport of the data service.
          */
         public void onDataServiceBound(@TransportType int transport) {}
+
+        /**
+         * Called when SIM load state changed.
+         *
+         * @param simState The current SIM state
+         */
+        public void onSimStateChanged(@SimState int simState) {}
+
+        /**
+         * Called when QosBearerSessions changed.
+         *
+         * @param qosBearerSessions The latest QOS bearer sessions.
+         */
+        public void onQosSessionsChanged(@NonNull List<QosBearerSession> qosBearerSessions) {}
     }
 
     /**
@@ -786,10 +827,13 @@
      * @param phone The phone instance.
      * @param looper The looper to be used by the handler. Currently the handler thread is the
      * phone process's main thread.
+     * @param featureFlags The feature flag.
      */
-    public DataNetworkController(@NonNull Phone phone, @NonNull Looper looper) {
+    public DataNetworkController(@NonNull Phone phone, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags) {
         super(looper);
         mPhone = phone;
+        mFeatureFlags = featureFlags;
         mLogTag = "DNC-" + mPhone.getPhoneId();
         log("DataNetworkController created.");
 
@@ -798,7 +842,7 @@
             mDataServiceManagers.put(transport, new DataServiceManager(mPhone, looper, transport));
         }
 
-        mDataConfigManager = new DataConfigManager(mPhone, looper);
+        mDataConfigManager = new DataConfigManager(mPhone, looper, featureFlags);
 
         // ========== Anomaly counters ==========
         mImsThrottleCounter = new SlidingWindowEventCounter(
@@ -864,6 +908,7 @@
                 DataProfileManager.class.getName())
                 .makeDataProfileManager(mPhone, this, mDataServiceManagers
                                 .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), looper,
+                        mFeatureFlags,
                         new DataProfileManagerCallback(this::post) {
                             @Override
                             public void onDataProfilesChanged() {
@@ -884,7 +929,7 @@
                     }
                 });
         mDataRetryManager = new DataRetryManager(mPhone, this,
-                mDataServiceManagers, looper,
+                mDataServiceManagers, looper, mFeatureFlags,
                 new DataRetryManagerCallback(this::post) {
                     @Override
                     public void onDataNetworkSetupRetry(
@@ -1283,6 +1328,7 @@
      * {@link #onAttachNetworkRequestsFailed(DataNetwork, NetworkRequestList)} will be invoked.
      */
     private boolean findCompatibleDataNetworkAndAttach(@NonNull NetworkRequestList requestList) {
+        if (requestList.isEmpty()) return false;
         // Try to find a data network that can satisfy all the network requests.
         for (DataNetwork dataNetwork : mDataNetworkList) {
             TelephonyNetworkRequest networkRequest = requestList.stream()
@@ -1300,6 +1346,28 @@
             // When reaching here, it means this data network can satisfy all the network requests.
             logv("Found a compatible data network " + dataNetwork + ". Attaching "
                     + requestList);
+
+            // If WLAN preferred, see whether a more suitable data profile shall be used to satisfy
+            // a short-lived request that doesn't perform handover.
+            int capability = requestList.getFirst().getApnTypeNetworkCapability();
+            int preferredTransport = mAccessNetworksManager
+                    .getPreferredTransportByNetworkCapability(capability);
+            if (capability == NetworkCapabilities.NET_CAPABILITY_MMS
+                    && preferredTransport != dataNetwork.getTransport()
+                    && preferredTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+                DataProfile candidate = mDataProfileManager
+                        .getDataProfileForNetworkRequest(requestList.getFirst(),
+                                TelephonyManager.NETWORK_TYPE_IWLAN,
+                                mServiceState.isUsingNonTerrestrialNetwork(),
+                                isEsimBootStrapProvisioningActivated(),
+                                false/*ignorePermanentFailure*/);
+                if (candidate != null && !dataNetwork.getDataProfile().equals(candidate)) {
+                    logv("But skipped because found better data profile " + candidate
+                            + DataUtils.networkCapabilityToString(capability) + " preferred on "
+                            + AccessNetworkConstants.transportTypeToString(preferredTransport));
+                    continue;
+                }
+            }
             return dataNetwork.attachNetworkRequests(requestList);
         }
         return false;
@@ -1363,22 +1431,26 @@
     /**
      * Evaluate if telephony frameworks would allow data setup for internet in current environment.
      *
+     * @param ignoreExistingNetworks {@code true} to skip the existing network check.
      * @return {@code true} if the environment is allowed for internet data. {@code false} if not
      * allowed. For example, if SIM is absent, or airplane mode is on, then data is NOT allowed.
      * This API does not reflect the currently internet data network status. It's possible there is
      * no internet data due to weak cellular signal or network side issue, but internet data is
      * still allowed in this case.
      */
-    public boolean isInternetDataAllowed() {
+    public boolean isInternetDataAllowed(boolean ignoreExistingNetworks) {
         TelephonyNetworkRequest internetRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                         .build(), mPhone);
-        // If one of the existing networks can satisfy the internet request, then internet is
-        // allowed.
-        if (mDataNetworkList.stream().anyMatch(dataNetwork -> internetRequest.canBeSatisfiedBy(
-                dataNetwork.getNetworkCapabilities()))) {
+        // If we don't skip checking existing network, then we should check If one of the
+        // existing networks can satisfy the internet request, then internet is allowed.
+        if ((!mFeatureFlags.ignoreExistingNetworksForInternetAllowedChecking()
+                || !ignoreExistingNetworks)
+                && mDataNetworkList.stream().anyMatch(
+                        dataNetwork -> internetRequest.canBeSatisfiedBy(
+                                dataNetwork.getNetworkCapabilities()))) {
             return true;
         }
 
@@ -1454,7 +1526,9 @@
         // Bypass all checks for emergency network request.
         if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
             DataProfile emergencyProfile = mDataProfileManager.getDataProfileForNetworkRequest(
-                    networkRequest, getDataNetworkType(transport), true);
+                    networkRequest, getDataNetworkType(transport),
+                    mServiceState.isUsingNonTerrestrialNetwork(),
+                    isEsimBootStrapProvisioningActivated(), true);
 
             // Check if the profile is being throttled.
             if (mDataConfigManager.shouldHonorRetryTimerForEmergencyNetworkRequest()
@@ -1506,7 +1580,9 @@
             if (nri != null) {
                 DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
                 if (dsri != null && dsri.getVopsSupportInfo() != null
-                        && !dsri.getVopsSupportInfo().isVopsSupported()) {
+                        && !dsri.getVopsSupportInfo().isVopsSupported()
+                        && !mDataConfigManager.allowBringUpNetworkInNonVops(
+                                nri.getNetworkRegistrationState())) {
                     evaluation.addDataDisallowedReason(DataDisallowedReason.VOPS_NOT_SUPPORTED);
                 }
             }
@@ -1557,6 +1633,11 @@
             evaluation.addDataDisallowedReason(DataDisallowedReason.CDMA_EMERGENCY_CALLBACK_MODE);
         }
 
+        // Check whether data is disallowed while using satellite
+        if (isDataDisallowedDueToSatellite(networkRequest.getCapabilities())) {
+            evaluation.addDataDisallowedReason(DataDisallowedReason.SERVICE_OPTION_NOT_SUPPORTED);
+        }
+
         // Check if only one data network is allowed.
         if (isOnlySingleDataNetworkAllowed(transport)
                 && !hasCapabilityExemptsFromSinglePdnRule(networkRequest.getCapabilities())) {
@@ -1622,6 +1703,8 @@
         }
         DataProfile dataProfile = mDataProfileManager
                 .getDataProfileForNetworkRequest(networkRequest, networkType,
+                        mServiceState.isUsingNonTerrestrialNetwork(),
+                        isEsimBootStrapProvisioningActivated(),
                         // If the evaluation is due to environmental changes, then we should ignore
                         // the permanent failure reached earlier.
                         reason.isConditionBased());
@@ -1638,7 +1721,14 @@
         }
 
         if (!evaluation.containsDisallowedReasons()) {
-            evaluation.setCandidateDataProfile(dataProfile);
+            if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                    && isEsimBootStrapProvisioningActivated()
+                    && isEsimBootStrapMaxDataLimitReached()) {
+                log("BootStrap Sim Data Usage limit reached");
+                evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_LIMIT_REACHED);
+            } else {
+                evaluation.setCandidateDataProfile(dataProfile);
+            }
         }
 
         networkRequest.setEvaluation(evaluation);
@@ -1655,6 +1745,61 @@
     }
 
     /**
+     * This method
+     *  - 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()}
+     *
+     * @return true, if bootstrap sim data limit is reached
+     *         else false, if bootstrap sim max data limit allowed set is -1(Unlimited) or current
+     *         bootstrap sim total data usage is less than bootstrap sim max data limit allowed.
+     *
+     */
+    private boolean isEsimBootStrapMaxDataLimitReached() {
+        long esimBootStrapMaxDataLimitBytes =
+                mDataConfigManager.getEsimBootStrapMaxDataLimitBytes();
+
+        if (esimBootStrapMaxDataLimitBytes < 0L) {
+            return false;
+        }
+
+        log("current bootstrap sim data Usage: " + mBootStrapSimTotalDataUsageBytes);
+        if (mBootStrapSimTotalDataUsageBytes >= esimBootStrapMaxDataLimitBytes) {
+            return true;
+        } else {
+            mBootStrapSimTotalDataUsageBytes = getDataUsage();
+            return mBootStrapSimTotalDataUsageBytes >= esimBootStrapMaxDataLimitBytes;
+        }
+    }
+
+    /**
+     * Query network usage statistics summaries based on {@link
+     * NetworkStatsManager#querySummaryForDevice(NetworkTemplate, long, long)}
+     *
+     * @return Data usage in bytes for the connected networks related to the current subscription
+     */
+    private long getDataUsage() {
+        NetworkStatsManager networkStatsManager =
+                        mPhone.getContext().getSystemService(NetworkStatsManager.class);
+
+        if (networkStatsManager != null) {
+            final NetworkTemplate.Builder builder =
+                    new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE);
+            final String subscriberId = mPhone.getSubscriberId();
+
+            if (!TextUtils.isEmpty(subscriberId)) {
+                builder.setSubscriberIds(Set.of(subscriberId));
+                NetworkTemplate template = builder.build();
+                final NetworkStats.Bucket ret = networkStatsManager
+                        .querySummaryForDevice(template, 0L, System.currentTimeMillis());
+                return ret.getRxBytes() + ret.getTxBytes();
+            }
+        }
+        return 0L;
+    }
+
+    /**
      * @return The grouped unsatisfied network requests. The network requests that have the same
      * network capabilities is grouped into one {@link NetworkRequestList}.
      */
@@ -1734,6 +1879,31 @@
             evaluation.addDataDisallowedReason(DataDisallowedReason.CDMA_EMERGENCY_CALLBACK_MODE);
         }
 
+        // Check whether data is disallowed while using satellite
+        if (isDataDisallowedDueToSatellite(dataNetwork.getNetworkCapabilities()
+                .getCapabilities())) {
+            evaluation.addDataDisallowedReason(DataDisallowedReason.SERVICE_OPTION_NOT_SUPPORTED);
+        }
+
+        // Check whether data limit reached for bootstrap sim, else re-evaluate based on the timer
+        // set.
+        if (isEsimBootStrapProvisioningActivated()
+                && dataNetwork.getTransport() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+            if (isEsimBootStrapMaxDataLimitReached()) {
+                log("BootStrap Sim Data Usage limit reached");
+                evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_LIMIT_REACHED);
+            } else {
+                if (!hasMessages(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)) {
+                    sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
+                            DataEvaluationReason.CHECK_DATA_USAGE),
+                            REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS);
+                } else {
+                    log("skip scheduling evaluating existing data networks since already"
+                            + "scheduled");
+                }
+            }
+        }
+
         // Check if there are other network that has higher priority, and only single data network
         // is allowed.
         if (isOnlySingleDataNetworkAllowed(dataNetwork.getTransport())
@@ -1756,6 +1926,8 @@
             }
         }
 
+        // It's recommended for IMS service not requesting MMTEL capability, so that MMTEL
+        // capability is dynamically added when moving between vops and nonvops area.
         boolean vopsIsRequired = dataNetwork.hasNetworkCapabilityInNetworkRequests(
                 NetworkCapabilities.NET_CAPABILITY_MMTEL);
 
@@ -1778,7 +1950,8 @@
                         DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
                         if (dsri != null && dsri.getVopsSupportInfo() != null
                                 && !dsri.getVopsSupportInfo().isVopsSupported()
-                                && !mDataConfigManager.shouldKeepNetworkUpInNonVops()) {
+                                && !mDataConfigManager.shouldKeepNetworkUpInNonVops(
+                                        nri.getNetworkRegistrationState())) {
                             evaluation.addDataDisallowedReason(
                                     DataDisallowedReason.VOPS_NOT_SUPPORTED);
                         }
@@ -1958,6 +2131,8 @@
                 }
 
                 // Check if VoPS is required, but the target transport is non-VoPS.
+                // It's recommended for IMS service not requesting MMTEL capability, so that MMTEL
+                // capability is dynamically added when moving between vops and nonvops area.
                 NetworkRequestList networkRequestList =
                         dataNetwork.getAttachedNetworkRequestList();
                 if (networkRequestList.stream().anyMatch(request
@@ -1966,7 +2141,8 @@
                     // Check if the network is non-VoPS.
                     if (dsri != null && dsri.getVopsSupportInfo() != null
                             && !dsri.getVopsSupportInfo().isVopsSupported()
-                            && !mDataConfigManager.shouldKeepNetworkUpInNonVops()) {
+                            && !mDataConfigManager.shouldKeepNetworkUpInNonVops(
+                                    nri.getNetworkRegistrationState())) {
                         dataEvaluation.addDataDisallowedReason(
                                 DataDisallowedReason.VOPS_NOT_SUPPORTED);
                     }
@@ -2076,6 +2252,8 @@
                     return DataNetwork.TEAR_DOWN_REASON_SIM_REMOVAL;
                 case CONCURRENT_VOICE_DATA_NOT_ALLOWED:
                     return DataNetwork.TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED;
+                case SERVICE_OPTION_NOT_SUPPORTED:
+                    return DataNetwork.TEAR_DOWN_REASON_SERVICE_OPTION_NOT_SUPPORTED;
                 case RADIO_POWER_OFF:
                     return DataNetwork.TEAR_DOWN_REASON_AIRPLANE_MODE_ON;
                 case PENDING_TEAR_DOWN_ALL:
@@ -2108,6 +2286,8 @@
                     return DataNetwork.TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK;
                 case HANDOVER_RETRY_STOPPED:
                     return DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED;
+                case DATA_LIMIT_REACHED:
+                    return DataNetwork.TEAR_DOWN_REASON_DATA_LIMIT_REACHED;
             }
         }
         return DataNetwork.TEAR_DOWN_REASON_NONE;
@@ -2122,8 +2302,7 @@
         for (DataNetwork dataNetwork : mDataNetworkList) {
             if (dataNetwork.getId() == cid
                     && dataNetwork.isConnected()
-                    && dataNetwork.getNetworkCapabilities()
-                    .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                    && dataNetwork.isInternetSupported()) {
                 return true;
             }
         }
@@ -2219,7 +2398,8 @@
     @Nullable
     public DataNetwork getDataNetworkByInterface(@NonNull String interfaceName) {
         return mDataNetworkList.stream()
-                .filter(dataNetwork -> !dataNetwork.isDisconnecting())
+                .filter(dataNetwork -> !(dataNetwork.isDisconnecting()
+                        || dataNetwork.isDisconnected()))
                 .filter(dataNetwork -> interfaceName.equals(
                         dataNetwork.getLinkProperties().getInterfaceName()))
                 .findFirst()
@@ -2227,6 +2407,22 @@
     }
 
     /**
+     * Check if the device is in eSIM bootstrap provisioning state.
+     *
+     * @return {@code true} if the device is under eSIM bootstrap provisioning.
+     */
+    public boolean isEsimBootStrapProvisioningActivated() {
+        if (!mFeatureFlags.esimBootstrapProvisioningFlag()) {
+            return false;
+        }
+
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(mPhone.getSubId());
+        return subInfo != null
+                && subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+    }
+
+    /**
      * Register for IMS feature registration state.
      *
      * @param subId The subscription index.
@@ -2508,8 +2704,8 @@
                 + AccessNetworkConstants.transportTypeToString(transport) + " with " + dataProfile
                 + ", and attaching " + networkRequestList.size() + " network requests to it.");
 
-        mDataNetworkList.add(new DataNetwork(mPhone, getLooper(), mDataServiceManagers,
-                dataProfile, networkRequestList, transport, allowedReason,
+        mDataNetworkList.add(new DataNetwork(mPhone, mFeatureFlags, getLooper(),
+                mDataServiceManagers, dataProfile, networkRequestList, transport, allowedReason,
                 new DataNetworkCallback(this::post) {
                     @Override
                     public void onSetupDataFailed(@NonNull DataNetwork dataNetwork,
@@ -2598,6 +2794,14 @@
                         DataNetworkController.this.onRetryUnsatisfiedNetworkRequest(
                                 networkRequest);
                     }
+
+                    @Override
+                    public void onQosSessionsChanged(
+                            @NonNull List<QosBearerSession> qosBearerSessions) {
+                        mDataNetworkControllerCallbacks.forEach(
+                                callback -> callback.invokeFromExecutor(() ->
+                                        callback.onQosSessionsChanged(qosBearerSessions)));
+                    }
                 }
         ));
         if (!mAnyDataNetworkExisting) {
@@ -2712,7 +2916,7 @@
             mPreviousConnectedDataNetworkList.remove(MAX_HISTORICAL_CONNECTED_DATA_NETWORKS);
         }
 
-        updateOverallInternetDataState();
+        if (dataNetwork.isInternetSupported()) updateOverallInternetDataState();
 
         if (dataNetwork.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_IMS)) {
@@ -2720,6 +2924,12 @@
                     + TelephonyUtils.dataStateToString(mImsDataNetworkState) + " to CONNECTED.");
             mImsDataNetworkState = TelephonyManager.DATA_CONNECTED;
         }
+
+        if (isEsimBootStrapProvisioningActivated()) {
+            sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
+                    DataEvaluationReason.CHECK_DATA_USAGE),
+                    REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS);
+        }
     }
 
     /**
@@ -2904,7 +3114,7 @@
      */
     private void onDataNetworkSuspendedStateChanged(@NonNull DataNetwork dataNetwork,
             boolean suspended) {
-        updateOverallInternetDataState();
+        if (dataNetwork.isInternetSupported()) updateOverallInternetDataState();
 
         if (dataNetwork.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_IMS)) {
@@ -2931,7 +3141,7 @@
         mDataNetworkList.remove(dataNetwork);
         mPendingImsDeregDataNetworks.remove(dataNetwork);
         mDataRetryManager.cancelPendingHandoverRetry(dataNetwork);
-        updateOverallInternetDataState();
+        if (dataNetwork.isInternetSupported()) updateOverallInternetDataState();
 
         if (dataNetwork.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_IMS)) {
@@ -3140,6 +3350,8 @@
                 sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
                         DataEvaluationReason.SIM_LOADED));
             }
+            mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                    () -> callback.onSimStateChanged(mSimState)));
         }
     }
 
@@ -3201,9 +3413,12 @@
             logl("Start handover " + dataNetwork + " to "
                     + AccessNetworkConstants.transportTypeToString(targetTransport));
             dataNetwork.startHandover(targetTransport, dataHandoverRetryEntry);
-        } else if (dataEvaluation.containsOnly(DataDisallowedReason.NOT_IN_SERVICE)
-                && dataNetwork.shouldDelayImsTearDownDueToInCall()) {
-            // We try to preserve voice call in the case of temporary preferred transport mismatch
+        } else if (dataNetwork.shouldDelayImsTearDownDueToInCall()
+                && (dataEvaluation.containsOnly(DataDisallowedReason.NOT_IN_SERVICE)
+                || mFeatureFlags.relaxHoTeardown() && dataEvaluation.isSubsetOf(
+                        DataDisallowedReason.NOT_IN_SERVICE,
+                        DataDisallowedReason.NOT_ALLOWED_BY_POLICY))) {
+            // We try our best to preserve the voice call by retrying later
             if (dataHandoverRetryEntry != null) {
                 dataHandoverRetryEntry.setState(DataRetryEntry.RETRY_STATE_FAILED);
             }
@@ -3325,8 +3540,7 @@
             dataNetwork.attachNetworkRequests(networkRequestList);
         }
 
-        if (dataNetwork.getNetworkCapabilities().hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+        if (dataNetwork.isInternetSupported()) {
             // Update because DataNetwork#isInternetSupported might have changed with capabilities.
             updateOverallInternetDataState();
         }
@@ -3349,7 +3563,12 @@
         }
 
         if (oldNri.getAccessNetworkTechnology() != newNri.getAccessNetworkTechnology()
-                || (!oldNri.isRoaming() && newNri.isRoaming())) {
+                // Some CarrierConfig disallows vops in nonVops area for specified home/roaming.
+                || (oldNri.isRoaming() != newNri.isRoaming())) {
+            return true;
+        }
+
+        if (!oldNri.isNonTerrestrialNetwork() && newNri.isNonTerrestrialNetwork()) {
             return true;
         }
 
@@ -3395,7 +3614,8 @@
         if (oldPsNri == null
                 || oldPsNri.getAccessNetworkTechnology() != newPsNri.getAccessNetworkTechnology()
                 || (!oldPsNri.isInService() && newPsNri.isInService())
-                || (oldPsNri.isRoaming() && !newPsNri.isRoaming())) {
+                // Some CarrierConfig allows vops in nonVops area for specified home/roaming.
+                || (oldPsNri.isRoaming() != newPsNri.isRoaming())) {
             return true;
         }
 
@@ -3405,6 +3625,10 @@
             return true;
         }
 
+        if (oldSS.isUsingNonTerrestrialNetwork() && !newSS.isUsingNonTerrestrialNetwork()) {
+            return true;
+        }
+
         DataSpecificRegistrationInfo oldDsri = oldPsNri.getDataSpecificInfo();
         DataSpecificRegistrationInfo newDsri = newPsNri.getDataSpecificInfo();
 
@@ -3453,7 +3677,12 @@
                                 oldNri.getRegistrationState()) : null);
                 debugMessage.append("->").append(newNri != null
                         ? NetworkRegistrationInfo.registrationStateToString(
-                        newNri.getRegistrationState()) : null).append("] ");
+                        newNri.getRegistrationState()) : null).append(", ");
+                debugMessage.append(oldNri != null ? NetworkRegistrationInfo
+                        .isNonTerrestrialNetworkToString(oldNri.isNonTerrestrialNetwork()) : null);
+                debugMessage.append("->").append(newNri != null ? NetworkRegistrationInfo
+                        .isNonTerrestrialNetworkToString(newNri.isNonTerrestrialNetwork()) : null)
+                        .append("] ");
                 if (shouldReevaluateDataNetworks(oldNri, newNri)) {
                     if (!hasMessages(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)) {
                         sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
@@ -3490,11 +3719,11 @@
                 .anyMatch(dataNetwork -> dataNetwork.isInternetSupported()
                         && (dataNetwork.isConnected() || dataNetwork.isHandoverInProgress()));
         // If any one is not suspended, then the overall is not suspended.
-        List<DataNetwork> allConnectedInternetDataNetworks = mDataNetworkList.stream()
+        Set<DataNetwork> allConnectedInternetDataNetworks = mDataNetworkList.stream()
                 .filter(DataNetwork::isInternetSupported)
                 .filter(dataNetwork -> dataNetwork.isConnected()
                         || dataNetwork.isHandoverInProgress())
-                .collect(Collectors.toList());
+                .collect(Collectors.toSet());
         boolean isSuspended = !allConnectedInternetDataNetworks.isEmpty()
                 && allConnectedInternetDataNetworks.stream().allMatch(DataNetwork::isSuspended);
         logv("isSuspended=" + isSuspended + ", anyInternetConnected=" + anyInternetConnected
@@ -3511,19 +3740,15 @@
             logl("Internet data state changed from "
                     + TelephonyUtils.dataStateToString(mInternetDataNetworkState) + " to "
                     + TelephonyUtils.dataStateToString(dataNetworkState) + ".");
-            // TODO: Create a new route to notify TelephonyRegistry.
-            if (dataNetworkState == TelephonyManager.DATA_CONNECTED
-                    && mInternetDataNetworkState == TelephonyManager.DATA_DISCONNECTED) {
-                mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
-                        () -> callback.onInternetDataNetworkConnected(
-                                allConnectedInternetDataNetworks)));
-            } else if (dataNetworkState == TelephonyManager.DATA_DISCONNECTED
-                    && mInternetDataNetworkState == TelephonyManager.DATA_CONNECTED) {
-                mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
-                        callback::onInternetDataNetworkDisconnected));
-            } // TODO: Add suspended callback if needed.
             mInternetDataNetworkState = dataNetworkState;
         }
+        // Check data network reference equality to update current connected internet networks.
+        if (!mConnectedInternetNetworks.equals(allConnectedInternetDataNetworks)) {
+            mConnectedInternetNetworks = allConnectedInternetDataNetworks;
+            mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                    () -> callback.onConnectedInternetDataNetworksChanged(
+                            allConnectedInternetDataNetworks)));
+        }
     }
 
     /**
@@ -3695,6 +3920,11 @@
      * de-registered yet.
      */
     private boolean isSafeToTearDown(@NonNull DataNetwork dataNetwork) {
+        if (dataNetwork.hasNetworkCapabilityInNetworkRequests(
+                NetworkCapabilities.NET_CAPABILITY_EIMS)) {
+            // FWK currently doesn't track emergency registration state for graceful tear down.
+            return true;
+        }
         for (int imsFeature : SUPPORTED_IMS_FEATURES) {
             String imsFeaturePackage = mImsFeaturePackageName.get(imsFeature);
             if (imsFeaturePackage != null) {
@@ -3771,6 +4001,78 @@
     }
 
     /**
+     * Check whether data is disallowed while using satellite
+     * @param capabilities An array of the NetworkCapabilities to be checked
+     * @return {@code true} if the capabilities contain any capability that are restricted
+     * while using satellite else {@code false}
+     */
+    private boolean isDataDisallowedDueToSatellite(@NetCapability int[] capabilities) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            return false;
+        }
+
+        if (!mServiceState.isUsingNonTerrestrialNetwork()) {
+            // Device is not connected to satellite
+            return false;
+        }
+
+        Set<Integer> restrictedCapabilities = Set.of(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        if (Arrays.stream(capabilities).noneMatch(restrictedCapabilities::contains)) {
+            // Only internet data disallowed while using satellite
+            return false;
+        }
+
+        for (NetworkRegistrationInfo nri : mServiceState.getNetworkRegistrationInfoList()) {
+            if (nri.isNonTerrestrialNetwork()
+                    && nri.getAvailableServices().contains(
+                            NetworkRegistrationInfo.SERVICE_TYPE_DATA)) {
+                // Data is supported while using satellite
+                return false;
+            }
+        }
+
+        // Data is disallowed while using satellite
+        return true;
+    }
+
+    /**
+     * Request network validation.
+     *
+     * Nnetwork validation request is sent to the DataNetwork that matches the network capability
+     * in the list of DataNetwork owned by the DNC.
+     *
+     * @param capability network capability {@link NetCapability}
+     */
+    public void requestNetworkValidation(@NetCapability int capability,
+            @NonNull Consumer<Integer> resultCodeCallback) {
+
+        if (DataUtils.networkCapabilityToApnType(capability) == ApnSetting.TYPE_NONE) {
+            // If the capability is not an apn type based capability, sent an invalid argument.
+            loge("requestNetworkValidation: the capability is not an apn type based. capability:"
+                    + capability);
+            FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+                    .accept(DataServiceCallback.RESULT_ERROR_INVALID_ARG);
+            return;
+        }
+
+        // Find DataNetwork that matches the capability.
+        List<DataNetwork> list = mDataNetworkList.stream()
+                .filter(dataNetwork ->
+                        dataNetwork.getNetworkCapabilities().hasCapability(capability))
+                .toList();
+
+        if (!list.isEmpty()) {
+            // request network validation.
+            list.forEach(dataNetwork -> dataNetwork.requestNetworkValidation(resultCodeCallback));
+        } else {
+            // If not found, sent an invalid argument.
+            loge("requestNetworkValidation: No matching DataNetwork was found");
+            FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+                    .accept(DataServiceCallback.RESULT_ERROR_INVALID_ARG);
+        }
+    }
+
+    /**
      * Log debug messages.
      * @param s debug messages
      */
@@ -3873,6 +4175,7 @@
                 .map(TelephonyManager::getNetworkTypeName).collect(Collectors.joining(",")));
         pw.println("mImsThrottleCounter=" + mImsThrottleCounter);
         pw.println("mNetworkUnwantedCounter=" + mNetworkUnwantedCounter);
+        pw.println("mBootStrapSimTotalDataUsageBytes=" + mBootStrapSimTotalDataUsageBytes);
         pw.println("Local logs:");
         pw.increaseIndent();
         mLocalLog.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index e7e9204..51fc71b 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -34,8 +34,10 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.SimState;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataProfile;
 import android.telephony.data.TrafficDescriptor;
@@ -47,6 +49,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -109,6 +112,12 @@
     private final @NonNull Set<DataProfileManagerCallback> mDataProfileManagerCallbacks =
             new ArraySet<>();
 
+    /** SIM state. */
+    private @SimState int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
+
+    /** Feature flags controlling which feature is enabled. */
+    private final @NonNull FeatureFlags mFeatureFlags;
+
     /**
      * Data profile manager callback. This should be only used by {@link DataNetworkController}.
      */
@@ -136,15 +145,18 @@
      * @param dataServiceManager WWAN data service manager.
      * @param looper The looper to be used by the handler. Currently the handler thread is the
      * phone process's main thread.
+     * @param featureFlags Feature flags controlling which feature is enabled.
      * @param callback Data profile manager callback.
      */
     public DataProfileManager(@NonNull Phone phone,
             @NonNull DataNetworkController dataNetworkController,
             @NonNull DataServiceManager dataServiceManager, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
             @NonNull DataProfileManagerCallback callback) {
         super(looper);
         mPhone = phone;
         mLogTag = "DPM-" + mPhone.getPhoneId();
+        mFeatureFlags = featureFlags;
         mDataNetworkController = dataNetworkController;
         mWwanDataServiceManager = dataServiceManager;
         mDataConfigManager = dataNetworkController.getDataConfigManager();
@@ -159,10 +171,16 @@
         mDataNetworkController.registerDataNetworkControllerCallback(
                 new DataNetworkControllerCallback(this::post) {
                     @Override
-                    public void onInternetDataNetworkConnected(
-                            @NonNull List<DataNetwork> internetNetworks) {
+                    public void onConnectedInternetDataNetworksChanged(
+                            @NonNull Set<DataNetwork> internetNetworks) {
+                        if (internetNetworks.isEmpty()) return;
                         DataProfileManager.this.onInternetDataNetworkConnected(internetNetworks);
                     }
+
+                    @Override
+                    public void onSimStateChanged(@SimState int simState) {
+                        DataProfileManager.this.mSimState = simState;
+                    }
                 });
         mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) {
             @Override
@@ -238,6 +256,7 @@
         cursor.close();
         return dataProfile;
     }
+
     /**
      * Update all data profiles, including preferred data profile, and initial attach data profile.
      * Also send those profiles down to the modem if needed.
@@ -268,7 +287,8 @@
                     log("Added " + dataProfile);
 
                     isInternetSupported |= apn.canHandleType(ApnSetting.TYPE_DEFAULT);
-                    if (mDataConfigManager.isApnConfigAnomalyReportEnabled()) {
+                    if (mDataConfigManager.isApnConfigAnomalyReportEnabled()
+                            && apn.getEditedStatus() == Telephony.Carriers.UNEDITED) {
                         checkApnSetting(apn);
                     }
                 }
@@ -285,7 +305,7 @@
 
         DataProfile dataProfile;
 
-        if (!profiles.isEmpty()) { // APN database has been read successfully after SIM loaded
+        if (mSimState == TelephonyManager.SIM_STATE_LOADED) {
             // Check if any of the profile already supports IMS, if not, add the default one.
             dataProfile = profiles.stream()
                     .filter(dp -> dp.canSatisfy(NetworkCapabilities.NET_CAPABILITY_IMS))
@@ -396,27 +416,40 @@
     }
 
     /**
-     * Called when internet data is connected.
+     * Called when new internet data connect.
      *
      * @param internetNetworks The connected internet data networks.
      */
-    private void onInternetDataNetworkConnected(@NonNull List<DataNetwork> internetNetworks) {
+    private void onInternetDataNetworkConnected(@NonNull Set<DataNetwork> internetNetworks) {
         DataProfile defaultProfile = null;
-        if (internetNetworks.size() == 1) {
+        if (mFeatureFlags.refinePreferredDataProfileSelection()) {
             // Most of the cases there should be only one.
-            defaultProfile = internetNetworks.get(0).getDataProfile();
-        } else if (internetNetworks.size() > 1) {
             // but in case there are multiple, find the default internet network, and choose the
             // one which has longest life cycle.
-            logv("onInternetDataNetworkConnected: mPreferredDataProfile=" + mPreferredDataProfile
-                    + " internetNetworks=" + internetNetworks);
             defaultProfile = internetNetworks.stream()
                     .filter(network -> mPreferredDataProfile == null
+                            // Find the one most resembles the current preferred profile,
+                            // avoiding e.g. DUN default network.
                             || canPreferredDataProfileSatisfy(
                             network.getAttachedNetworkRequestList()))
                     .map(DataNetwork::getDataProfile)
                     .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp))
                     .orElse(null);
+        } else {
+            if (internetNetworks.size() == 1) {
+                // Most of the cases there should be only one.
+                defaultProfile = internetNetworks.stream().findFirst().get().getDataProfile();
+            } else if (internetNetworks.size() > 1) {
+                // but in case there are multiple, find the default internet network, and choose the
+                // one which has longest life cycle.
+                defaultProfile = internetNetworks.stream()
+                        .filter(network -> mPreferredDataProfile == null
+                                || canPreferredDataProfileSatisfy(
+                                network.getAttachedNetworkRequestList()))
+                        .map(DataNetwork::getDataProfile)
+                        .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp))
+                        .orElse(null);
+            }
         }
 
         // Update a working internet data profile as a future candidate for preferred data profile
@@ -427,6 +460,9 @@
         // brought up a network means it passed sophisticated checks, update the preferred data
         // profile so that this network won't be torn down in future network evaluations.
         if (defaultProfile == null || defaultProfile.equals(mPreferredDataProfile)) return;
+        logv("onInternetDataNetworkConnected: defaultProfile=" + defaultProfile
+                + " previous preferredDataProfile=" + mPreferredDataProfile
+                + " internetNetworks=" + internetNetworks);
         // Save the preferred data profile into database.
         setPreferredDataProfile(defaultProfile);
         updateDataProfiles(false/*force update IA*/);
@@ -618,18 +654,19 @@
      *
      * @param networkRequest The network request.
      * @param networkType The current data network type.
+     * @param isNtn {@code true} if the device is currently attached to non-terrestrial network.
      * @param ignorePermanentFailure {@code true} to ignore {@link ApnSetting#getPermanentFailed()}.
      * This should be set to true for condition-based retry/setup.
      * @return The data profile. {@code null} if can't find any satisfiable data profile.
      */
     public @Nullable DataProfile getDataProfileForNetworkRequest(
             @NonNull TelephonyNetworkRequest networkRequest, @NetworkType int networkType,
-            boolean ignorePermanentFailure) {
+            boolean isNtn, boolean isEsimBootstrapProvisioning, boolean ignorePermanentFailure) {
         ApnSetting apnSetting = null;
         if (networkRequest.hasAttribute(TelephonyNetworkRequest
                 .CAPABILITY_ATTRIBUTE_APN_SETTING)) {
-            apnSetting = getApnSettingForNetworkRequest(networkRequest, networkType,
-                    ignorePermanentFailure);
+            apnSetting = getApnSettingForNetworkRequest(networkRequest, networkType, isNtn,
+                    isEsimBootstrapProvisioning, ignorePermanentFailure);
         }
 
         TrafficDescriptor.Builder trafficDescriptorBuilder = new TrafficDescriptor.Builder();
@@ -688,32 +725,60 @@
      *
      * @param networkRequest The network request.
      * @param networkType The current data network type.
+     * @param isNtn {@code true} if the device is currently attached to non-terrestrial network.
      * @param ignorePermanentFailure {@code true} to ignore {@link ApnSetting#getPermanentFailed()}.
      * This should be set to true for condition-based retry/setup.
      * @return The APN setting. {@code null} if can't find any satisfiable data profile.
      */
     private @Nullable ApnSetting getApnSettingForNetworkRequest(
             @NonNull TelephonyNetworkRequest networkRequest, @NetworkType int networkType,
-            boolean ignorePermanentFailure) {
+            boolean isNtn, boolean isEsimBootStrapProvisioning, boolean ignorePermanentFailure) {
         if (!networkRequest.hasAttribute(
                 TelephonyNetworkRequest.CAPABILITY_ATTRIBUTE_APN_SETTING)) {
             loge("Network request does not have APN setting attribute.");
             return null;
         }
 
-        // If the preferred data profile can be used, always use it if it can satisfy the network
-        // request with current network type (even though it's been marked as permanent failed.)
-        if (mPreferredDataProfile != null
-                && networkRequest.canBeSatisfiedBy(mPreferredDataProfile)
-                && mPreferredDataProfile.getApnSetting() != null
-                && mPreferredDataProfile.getApnSetting().canSupportNetworkType(networkType)) {
-            if (ignorePermanentFailure || !mPreferredDataProfile.getApnSetting()
-                    .getPermanentFailed()) {
-                return mPreferredDataProfile.getApnSetting();
+        // if esim bootstrap provisioning in progress, do not apply preferred data profile
+        if (!isEsimBootStrapProvisioning) {
+            if (mFeatureFlags.carrierEnabledSatelliteFlag()) {
+                // If the preferred data profile can be used, always use it if it can satisfy the
+                // network request with current network type (even though it's been marked as
+                // permanent failed.)
+                if (mPreferredDataProfile != null
+                        && networkRequest.canBeSatisfiedBy(mPreferredDataProfile)
+                        && mPreferredDataProfile.getApnSetting() != null
+                        && mPreferredDataProfile.getApnSetting().canSupportNetworkType(networkType)
+                        && ((isNtn && mPreferredDataProfile.getApnSetting().isForInfrastructure(
+                        ApnSetting.INFRASTRUCTURE_SATELLITE))
+                        || (!isNtn && mPreferredDataProfile.getApnSetting().isForInfrastructure(
+                        ApnSetting.INFRASTRUCTURE_CELLULAR)))) {
+                    if (ignorePermanentFailure || !mPreferredDataProfile.getApnSetting()
+                            .getPermanentFailed()) {
+                        return mPreferredDataProfile.getApnSetting();
+                    }
+                    log("The preferred data profile is permanently failed. Only condition based "
+                            + "retry can happen.");
+                    return null;
+                }
+            } else {
+                // If the preferred data profile can be used, always use it if it can satisfy the
+                // network request with current network type (even though it's been marked as
+                // permanent failed.)
+                if (mPreferredDataProfile != null
+                        && networkRequest.canBeSatisfiedBy(mPreferredDataProfile)
+                        && mPreferredDataProfile.getApnSetting() != null
+                        && mPreferredDataProfile.getApnSetting()
+                        .canSupportNetworkType(networkType)) {
+                    if (ignorePermanentFailure || !mPreferredDataProfile.getApnSetting()
+                            .getPermanentFailed()) {
+                        return mPreferredDataProfile.getApnSetting();
+                    }
+                    log("The preferred data profile is permanently failed. Only condition based "
+                            + "retry can happen.");
+                    return null;
+                }
             }
-            log("The preferred data profile is permanently failed. Only condition based retry "
-                    + "can happen.");
-            return null;
         }
 
         // Filter out the data profile that can't satisfy the request.
@@ -735,12 +800,33 @@
 
         // Check if the remaining data profiles can used in current data network type.
         dataProfiles = dataProfiles.stream()
-                .filter(dp -> dp.getApnSetting() != null
-                        && dp.getApnSetting().canSupportNetworkType(networkType))
+                .filter((dp) -> {
+                    if (dp.getApnSetting() == null) return false;
+                    if (!dp.getApnSetting().canSupportNetworkType(networkType)) return false;
+                    if (isEsimBootStrapProvisioning
+                            != dp.getApnSetting().isEsimBootstrapProvisioning()) return false;
+                    if (mFeatureFlags.carrierEnabledSatelliteFlag()) {
+                        if (isNtn && !dp.getApnSetting().isForInfrastructure(
+                                ApnSetting.INFRASTRUCTURE_SATELLITE)) {
+                            return false;
+                        }
+                        if (!isNtn && !dp.getApnSetting().isForInfrastructure(
+                                ApnSetting.INFRASTRUCTURE_CELLULAR)) {
+                            return false;
+                        }
+                    }
+
+                    return true;
+                })
                 .collect(Collectors.toList());
         if (dataProfiles.size() == 0) {
+            String ntnReason = "";
+            if (mFeatureFlags.carrierEnabledSatelliteFlag()) {
+                ntnReason = " and infrastructure for "
+                        + NetworkRegistrationInfo.isNonTerrestrialNetworkToString(isNtn);
+            }
             log("Can't find any data profile for network type "
-                    + TelephonyManager.getNetworkTypeName(networkType));
+                    + TelephonyManager.getNetworkTypeName(networkType) + ntnReason);
             return null;
         }
 
@@ -766,6 +852,10 @@
             return null;
         }
 
+        if (isEsimBootStrapProvisioning) {
+            log("Found esim bootstrap provisioning data profile for network request: "
+                    + dataProfiles.get(0).getApnSetting());
+        }
         return dataProfiles.get(0).getApnSetting();
     }
 
@@ -810,7 +900,10 @@
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
                         .build(), mPhone);
-        return getDataProfileForNetworkRequest(networkRequest, networkType, true) != null;
+        return getDataProfileForNetworkRequest(networkRequest, networkType,
+                mPhone.getServiceState().isUsingNonTerrestrialNetwork(),
+                mDataNetworkController.isEsimBootStrapProvisioningActivated(),
+                true) != null;
     }
 
     /**
@@ -970,6 +1063,7 @@
         // The following fields in apn1 and apn2 should be the same, otherwise ApnSetting.similar()
         // should fail earlier.
         apnBuilder.setApnName(apn1.getApnName());
+        apnBuilder.setOperatorNumeric(apn1.getOperatorNumeric());
         apnBuilder.setProtocol(apn1.getProtocol());
         apnBuilder.setRoamingProtocol(apn1.getRoamingProtocol());
         apnBuilder.setCarrierEnabled(apn1.isEnabled());
@@ -984,6 +1078,8 @@
         apnBuilder.setCarrierId(apn1.getCarrierId());
         apnBuilder.setSkip464Xlat(apn1.getSkip464Xlat());
         apnBuilder.setAlwaysOn(apn1.isAlwaysOn());
+        apnBuilder.setInfrastructureBitmask(apn1.getInfrastructureBitmask());
+        apnBuilder.setEsimBootstrapProvisioning(apn1.isEsimBootstrapProvisioning());
 
         return new DataProfile.Builder()
                 .setApnSetting(apnBuilder.build())
@@ -992,6 +1088,16 @@
     }
 
     /**
+     * Called by {@link DataRetryManager} to clear all permanent failures upon reset.
+     */
+    public void clearAllDataProfilePermanentFailures() {
+        mAllDataProfiles.stream()
+                .map(DataProfile::getApnSetting)
+                .filter(Objects::nonNull)
+                .forEach(apnSetting -> apnSetting.setPermanentFailed(false));
+    }
+
+    /**
      * Check if the provided data profile is still compatible with the current environment. Note
      * this method ignores APN id check and traffic descriptor check. A data profile with traffic
      * descriptor only can always be used in any condition.
diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java
index d88e6c6..5933463 100644
--- a/src/java/com/android/internal/telephony/data/DataRetryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java
@@ -56,6 +56,7 @@
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
 import com.android.internal.telephony.data.DataProfileManager.DataProfileManagerCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -151,6 +152,9 @@
     /** The phone instance. */
     private final @NonNull Phone mPhone;
 
+    /** Featureflags. */
+    private final @NonNull FeatureFlags mFlags;
+
     /** The RIL instance. */
     private final @NonNull CommandsInterface mRil;
 
@@ -962,11 +966,12 @@
     public DataRetryManager(@NonNull Phone phone,
             @NonNull DataNetworkController dataNetworkController,
             @NonNull SparseArray<DataServiceManager> dataServiceManagers,
-            @NonNull Looper looper,
+            @NonNull Looper looper, @NonNull FeatureFlags flags,
             @NonNull DataRetryManagerCallback dataRetryManagerCallback) {
         super(looper);
         mPhone = phone;
         mRil = phone.mCi;
+        mFlags = flags;
         mLogTag = "DRM-" + mPhone.getPhoneId();
         mDataRetryManagerCallbacks.add(dataRetryManagerCallback);
 
@@ -1019,18 +1024,21 @@
         mRil.registerForOn(this, EVENT_RADIO_ON, null);
         mRil.registerForModemReset(this, EVENT_MODEM_RESET, null);
 
-        // Register intent of alarm manager for long retry timer
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION_RETRY);
-        mPhone.getContext().registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (ACTION_RETRY.equals(intent.getAction())) {
-                    DataRetryManager.this.onAlarmIntentRetry(
-                            intent.getIntExtra(ACTION_RETRY_EXTRA_HASHCODE, -1 /*Bad hashcode*/));
+        if (!mFlags.useAlarmCallback()) {
+            // Register intent of alarm manager for long retry timer
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(ACTION_RETRY);
+            mPhone.getContext().registerReceiver(new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (ACTION_RETRY.equals(intent.getAction())) {
+                        DataRetryManager.this.onAlarmIntentRetry(
+                                intent.getIntExtra(ACTION_RETRY_EXTRA_HASHCODE,
+                                        -1 /*Bad hashcode*/));
+                    }
                 }
-            }
-        }, intentFilter);
+            }, intentFilter);
+        }
 
         if (mDataConfigManager.shouldResetDataThrottlingWhenTacChanges()) {
             mPhone.getServiceStateTracker().registerForAreaCodeChanged(this, EVENT_TAC_CHANGED,
@@ -1358,6 +1366,9 @@
         logl("Remove all retry and throttling entries, reason=" + resetReasonToString(reason));
         removeMessages(EVENT_DATA_SETUP_RETRY);
         removeMessages(EVENT_DATA_HANDOVER_RETRY);
+
+        mDataProfileManager.clearAllDataProfilePermanentFailures();
+
         mDataRetryEntries.stream()
                 .filter(entry -> entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED)
                 .forEach(entry -> entry.setState(DataRetryEntry.RETRY_STATE_CANCELLED));
@@ -1448,8 +1459,7 @@
      * @param dataRetryEntry The data retry entry.
      */
     private void schedule(@NonNull DataRetryEntry dataRetryEntry) {
-        logl("Scheduled data retry " + dataRetryEntry
-                + " hashcode=" + dataRetryEntry.hashCode());
+        logl("Scheduled data retry " + dataRetryEntry + " hashcode=" + dataRetryEntry.hashCode());
         mDataRetryEntries.add(dataRetryEntry);
         if (mDataRetryEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) {
             // Discard the oldest retry entry.
@@ -1465,16 +1475,32 @@
                             ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry),
                     dataRetryEntry.retryDelayMillis);
         } else {
-            Intent intent = new Intent(ACTION_RETRY);
-            intent.putExtra(ACTION_RETRY_EXTRA_HASHCODE, dataRetryEntry.hashCode());
-            // No need to wake up the device, the retry can wait util next time the device wake up
-            // to save power.
-            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
-                    dataRetryEntry.retryElapsedTime,
-                    PendingIntent.getBroadcast(mPhone.getContext(),
-                            dataRetryEntry.hashCode() /*Unique identifier of this retry attempt*/,
-                            intent,
-                            PendingIntent.FLAG_IMMUTABLE));
+            if (mFlags.useAlarmCallback()) {
+                // No need to wake up the device, the retry can wait util next time the device wake
+                // up to save power.
+                mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
+                        dataRetryEntry.retryElapsedTime,
+                        "dataRetryHash-" + dataRetryEntry.hashCode() /*debug tag*/,
+                        Runnable::run,
+                        null /*worksource*/,
+                        () -> {
+                            logl("onAlarm retry " + dataRetryEntry);
+                            sendMessage(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
+                                            ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY,
+                                    dataRetryEntry));
+                        });
+            } else {
+                Intent intent = new Intent(ACTION_RETRY);
+                intent.putExtra(ACTION_RETRY_EXTRA_HASHCODE, dataRetryEntry.hashCode());
+                // No need to wake up the device, the retry can wait util next time the device wake
+                // up  to save power.
+                mAlarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
+                        dataRetryEntry.retryElapsedTime,
+                        PendingIntent.getBroadcast(mPhone.getContext(),
+                                dataRetryEntry.hashCode()/*Unique identifier of the retry attempt*/,
+                                intent,
+                                PendingIntent.FLAG_IMMUTABLE));
+            }
         }
     }
 
@@ -1519,7 +1545,8 @@
         // transport.
         mDataThrottlingEntries.removeIf(
                 throttlingEntry -> dataProfile.equals(throttlingEntry.dataProfile)
-                        && (throttlingEntry.transport == transport));
+                        && (!mFlags.unthrottleCheckTransport()
+                        || throttlingEntry.transport == transport));
 
         if (mDataThrottlingEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) {
             // If we don't see the anomaly report after U release, we should remove this check for
@@ -1582,7 +1609,7 @@
             // profile manager.
             Stream<DataThrottlingEntry> stream = mDataThrottlingEntries.stream();
             stream = stream.filter(entry -> entry.expirationTimeMillis > now
-                    && (entry.transport == transport));
+                    && (!mFlags.unthrottleCheckTransport() || entry.transport == transport));
             if (dataProfile.getApnSetting() != null) {
                 stream = stream
                         .filter(entry -> entry.dataProfile.getApnSetting() != null)
diff --git a/src/java/com/android/internal/telephony/data/DataServiceManager.java b/src/java/com/android/internal/telephony/data/DataServiceManager.java
index bb0e8c3..ee66a6a 100644
--- a/src/java/com/android/internal/telephony/data/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/data/DataServiceManager.java
@@ -55,6 +55,7 @@
 import android.telephony.data.TrafficDescriptor;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConfigurationManager;
 import com.android.internal.telephony.util.TelephonyUtils;
@@ -947,6 +948,47 @@
     }
 
     /**
+     * Request data network validation.
+     *
+     * <p>Validates a given data network to ensure that the network can work properly.
+     *
+     * <p>Depending on the {@link DataServiceCallback.ResultCode}, Listener can determine whether
+     * validation has been triggered, has an error or whether it is a feature that is not supported.
+     *
+     * @param cid The identifier of the data network which is provided in DataCallResponse
+     * @param onCompleteMessage The result message for this request. Null if the client does not
+     * care about the result.
+     */
+    public void requestNetworkValidation(int cid, @Nullable Message onCompleteMessage) {
+        if (DBG) log("requestNetworkValidation");
+        if (!mBound) {
+            loge("DataService is not bound.");
+            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+            return;
+        }
+
+        IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+            @Override
+            public void accept(int result) {
+                mMessageMap.remove(asBinder());
+                sendCompleteMessage(onCompleteMessage, result);
+            }
+        };
+        if (onCompleteMessage != null) {
+            mMessageMap.put(callback.asBinder(), onCompleteMessage);
+        }
+        try {
+            mIDataService.requestNetworkValidation(mPhone.getPhoneId(), cid, callback);
+        } catch (RemoteException e) {
+            loge("Cannot invoke requestNetworkValidation on data service.");
+            if (callback != null) {
+                mMessageMap.remove(callback.asBinder());
+            }
+            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+        }
+    }
+
+    /**
      * Register for data service binding status changed event.
      *
      * @param h The target to post the event message to.
diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
index f8365bb..e54f6d3 100644
--- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java
+++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
@@ -28,6 +28,7 @@
 import android.provider.Settings;
 import android.sysprop.TelephonyProperties;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.MobileDataPolicy;
@@ -208,7 +209,9 @@
             case EVENT_SUBSCRIPTIONS_CHANGED: {
                 mSubId = (int) msg.obj;
                 refreshEnabledMobileDataPolicy();
-                updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_USER);
+                updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_USER,
+                        mPhone.getContext().getOpPackageName(),
+                        SubscriptionManager.isValidSubscriptionId(mSubId));
                 mPhone.notifyUserMobileDataStateChanged(isUserDataEnabled());
                 break;
             }
@@ -342,15 +345,15 @@
     }
 
     private void updateDataEnabledAndNotify(@TelephonyManager.DataEnabledChangedReason int reason) {
-        updateDataEnabledAndNotify(reason, mPhone.getContext().getOpPackageName());
+        updateDataEnabledAndNotify(reason, mPhone.getContext().getOpPackageName(), false);
     }
 
     private void updateDataEnabledAndNotify(@TelephonyManager.DataEnabledChangedReason int reason,
-            @NonNull String callingPackage) {
+            @NonNull String callingPackage, boolean shouldNotify) {
         boolean prevDataEnabled = mIsDataEnabled;
         mIsDataEnabled = isDataEnabled(ApnSetting.TYPE_ALL);
         log("mIsDataEnabled=" + mIsDataEnabled + ", prevDataEnabled=" + prevDataEnabled);
-        if (!mInitialized || prevDataEnabled != mIsDataEnabled) {
+        if (!mInitialized || shouldNotify || prevDataEnabled != mIsDataEnabled) {
             if (!mInitialized) mInitialized = true;
             notifyDataEnabledChanged(mIsDataEnabled, reason, callingPackage);
         }
@@ -443,7 +446,8 @@
             mPhone.notifyUserMobileDataStateChanged(enabled);
             mDataSettingsManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
                     () -> callback.onUserDataEnabledChanged(enabled, callingPackage)));
-            updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_USER, callingPackage);
+            updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_USER,
+                    callingPackage, false);
         }
     }
 
@@ -474,7 +478,8 @@
         if (mDataEnabledSettings.get(TelephonyManager.DATA_ENABLED_REASON_POLICY) != enabled) {
             logl("PolicyDataEnabled changed to " + enabled + ", callingPackage=" + callingPackage);
             mDataEnabledSettings.put(TelephonyManager.DATA_ENABLED_REASON_POLICY, enabled);
-            updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_POLICY, callingPackage);
+            updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_POLICY,
+                    callingPackage, false);
         }
     }
 
@@ -488,7 +493,7 @@
             logl("CarrierDataEnabled changed to " + enabled + ", callingPackage=" + callingPackage);
             mDataEnabledSettings.put(TelephonyManager.DATA_ENABLED_REASON_CARRIER, enabled);
             updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_CARRIER,
-                    callingPackage);
+                    callingPackage, false);
         }
     }
 
@@ -502,7 +507,7 @@
             logl("ThermalDataEnabled changed to " + enabled + ", callingPackage=" + callingPackage);
             mDataEnabledSettings.put(TelephonyManager.DATA_ENABLED_REASON_THERMAL, enabled);
             updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_THERMAL,
-                    callingPackage);
+                    callingPackage, false);
         }
     }
 
@@ -750,23 +755,22 @@
         boolean isNonDds = mPhone.getSubId() != SubscriptionManagerService.getInstance()
                 .getDefaultDataSubId();
 
+        Phone defaultDataPhone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance()
+                .getPhoneId(SubscriptionManagerService.getInstance()
+                        .getDefaultDataSubId()));
+        boolean isDdsUserEnabled = defaultDataPhone != null && defaultDataPhone.isUserDataEnabled();
+
         // mobile data policy : data during call
         if (isMobileDataPolicyEnabled(TelephonyManager
                 .MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL)) {
-            overridden = overridden || isNonDds && mPhone.getState() != PhoneConstants.State.IDLE;
+            overridden |= isNonDds && isDdsUserEnabled
+                    && mPhone.getState() != PhoneConstants.State.IDLE;
         }
 
         // mobile data policy : auto data switch
         if (isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)) {
             // check user enabled data on the default data phone
-            Phone defaultDataPhone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance()
-                    .getPhoneId(SubscriptionManagerService.getInstance()
-                            .getDefaultDataSubId()));
-            if (defaultDataPhone == null) {
-                loge("isDataEnabledOverriddenForApn: unexpected defaultDataPhone is null");
-            } else {
-                overridden = overridden || isNonDds && defaultDataPhone.isUserDataEnabled();
-            }
+            overridden |= isNonDds && isDdsUserEnabled;
         }
         return overridden;
     }
diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index 375b250..893509c 100644
--- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.database.ContentObserver;
 import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -56,7 +57,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
-import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -191,7 +192,7 @@
     private boolean mMobileDataChangedToEnabledDuringDataStall;
     /** Whether attempted all recovery steps. */
     private boolean mIsAttemptedAllSteps;
-    /** Whether internet network connected. */
+    /** Whether internet network that require validation is connected. */
     private boolean mIsInternetNetworkConnected;
     /** The durations for current recovery action */
     private @ElapsedRealtimeLong long mTimeElapsedOfCurrentAction;
@@ -307,16 +308,26 @@
                     }
 
                     @Override
-                    public void onInternetDataNetworkConnected(
-                            @NonNull List<DataNetwork> internetNetworks) {
-                        mIsInternetNetworkConnected = true;
-                        logl("onInternetDataNetworkConnected");
-                    }
-
-                    @Override
-                    public void onInternetDataNetworkDisconnected() {
-                        mIsInternetNetworkConnected = false;
-                        logl("onInternetDataNetworkDisconnected");
+                    public void onConnectedInternetDataNetworksChanged(
+                            @NonNull Set<DataNetwork> internetNetworks) {
+                        boolean anyInternetRequireValidatedConnected = internetNetworks.stream()
+                                .anyMatch(nw -> {
+                                    NetworkCapabilities capabilities = nw.getNetworkCapabilities();
+                                    // Only track the networks that require validation.
+                                    // The criteria is base on NetworkMonitorUtils.java.
+                                    return capabilities.hasCapability(
+                                            NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                                            && capabilities.hasCapability(
+                                            NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                                            && capabilities.hasCapability(
+                                            NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+                                });
+                        if (mIsInternetNetworkConnected != anyInternetRequireValidatedConnected) {
+                            mIsInternetNetworkConnected = anyInternetRequireValidatedConnected;
+                            logl(mIsInternetNetworkConnected
+                                    ? "At Least One InternetDataNetwork Connected"
+                                    : "All InternetDataNetwork Disconnected");
+                        }
                     }
                 });
         mPhone.mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
@@ -361,6 +372,7 @@
                                     0) != 0) {
                         mIsAirPlaneModeEnableDuringDataStall = true;
                     }
+                    setRecoveryAction(mLastAction);
                 }
                 break;
             case EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED:
@@ -516,6 +528,7 @@
         // during data stalled.
         if (mDataStalled && enabled) {
             mMobileDataChangedToEnabledDuringDataStall = true;
+            setRecoveryAction(mLastAction);
         }
     }
 
@@ -770,7 +783,7 @@
             return false;
         }
 
-        if (!mDataNetworkController.isInternetDataAllowed()) {
+        if (!mDataNetworkController.isInternetDataAllowed(true/* ignoreExistingNetworks */)) {
             logl("skip data stall recovery as data not allowed.");
             return false;
         }
diff --git a/src/java/com/android/internal/telephony/data/DataUtils.java b/src/java/com/android/internal/telephony/data/DataUtils.java
index fc0c4f1..0dda7b5 100644
--- a/src/java/com/android/internal/telephony/data/DataUtils.java
+++ b/src/java/com/android/internal/telephony/data/DataUtils.java
@@ -90,7 +90,10 @@
                 return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
             case "PRIORITIZE_LATENCY":
                 return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
+            case "RCS":
+                return NetworkCapabilities.NET_CAPABILITY_RCS;
             default:
+                loge("Illegal network capability: " + capabilityString);
                 return -1;
         }
     }
@@ -106,7 +109,7 @@
     public static @NetCapability Set<Integer> getNetworkCapabilitiesFromString(
             @NonNull String capabilitiesString) {
         // e.g. "IMS|" is not allowed
-        if (!capabilitiesString.matches("(\\s*[a-zA-Z]+\\s*)(\\|\\s*[a-zA-Z]+\\s*)*")) {
+        if (!capabilitiesString.matches("(\\s*[a-zA-Z_]+\\s*)(\\|\\s*[a-zA-Z_]+\\s*)*")) {
             return Collections.singleton(-1);
         }
         return Arrays.stream(capabilitiesString.split("\\s*\\|\\s*"))
@@ -254,6 +257,8 @@
                 return ApnSetting.TYPE_VSIM;
             case NetworkCapabilities.NET_CAPABILITY_BIP:
                 return ApnSetting.TYPE_BIP;
+            case NetworkCapabilities.NET_CAPABILITY_RCS:
+                return ApnSetting.TYPE_RCS;
             default:
                 return ApnSetting.TYPE_NONE;
         }
@@ -295,6 +300,8 @@
                 return NetworkCapabilities.NET_CAPABILITY_VSIM;
             case ApnSetting.TYPE_ENTERPRISE:
                 return NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
+            case ApnSetting.TYPE_RCS:
+                return NetworkCapabilities.NET_CAPABILITY_RCS;
             default:
                 return -1;
         }
diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
index a5b3da2..8dc8098 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -28,6 +28,7 @@
 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_NONE;
+
 import static java.util.Arrays.copyOf;
 
 import android.annotation.NonNull;
@@ -83,6 +84,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 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.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.DataSwitch;
@@ -184,6 +186,7 @@
     private final @NonNull NetworkRequestList mNetworkRequestList = new NetworkRequestList();
     protected final RegistrantList mActivePhoneRegistrants;
     private final SubscriptionManagerService mSubscriptionManagerService;
+    private final @NonNull FeatureFlags mFlags;
     protected final Context mContext;
     private final LocalLog mLocalLog;
     protected PhoneState[] mPhoneStates;
@@ -194,8 +197,6 @@
     private int mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
     /** The reason for the last time changing preferred data sub **/
     private int mLastSwitchPreferredDataReason = -1;
-    /** {@code true} if we've displayed the notification the first time auto switch occurs **/
-    private boolean mDisplayedAutoSwitchNotification = false;
     private boolean mPendingSwitchNeedValidation;
     @VisibleForTesting
     public final CellularNetworkValidator.ValidationCallback mValidationCallback =
@@ -312,15 +313,17 @@
     // Default timeout value of network validation in millisecond.
     private final static int DEFAULT_VALIDATION_EXPIRATION_TIME = 2000;
 
+    /** Controller that tracks {@link TelephonyManager#MOBILE_DATA_POLICY_AUTO_DATA_SWITCH} */
+    @NonNull private final AutoDataSwitchController mAutoDataSwitchController;
+    /** Callback to deal with requests made by the auto data switch controller. */
+    @NonNull private final AutoDataSwitchController.AutoDataSwitchControllerCallback
+            mAutoDataSwitchCallback;
+
     private ConnectivityManager mConnectivityManager;
     private int mImsRegistrationTech = REGISTRATION_TECH_NONE;
 
     private List<Set<CommandException.Error>> mCurrentDdsSwitchFailure;
 
-    private AutoDataSwitchController mAutoDataSwitchController;
-    private AutoDataSwitchController.AutoDataSwitchControllerCallback
-            mAutoDataSwitchCallback;
-
     /** Data settings manager callback. Key is the phone id. */
     private final @NonNull Map<Integer, DataSettingsManagerCallback> mDataSettingsManagerCallbacks =
             new ArrayMap<>();
@@ -397,9 +400,10 @@
     /**
      * Method to create singleton instance.
      */
-    public static PhoneSwitcher make(int maxDataAttachModemCount, Context context, Looper looper) {
+    public static PhoneSwitcher make(int maxDataAttachModemCount, Context context, Looper looper,
+            @NonNull FeatureFlags flags) {
         if (sPhoneSwitcher == null) {
-            sPhoneSwitcher = new PhoneSwitcher(maxDataAttachModemCount, context, looper);
+            sPhoneSwitcher = new PhoneSwitcher(maxDataAttachModemCount, context, looper, flags);
         }
 
         return sPhoneSwitcher;
@@ -457,9 +461,11 @@
     }
 
     @VisibleForTesting
-    public PhoneSwitcher(int maxActivePhones, Context context, Looper looper) {
+    public PhoneSwitcher(int maxActivePhones, Context context, Looper looper,
+            @NonNull FeatureFlags featureFlags) {
         super(looper);
         mContext = context;
+        mFlags = featureFlags;
         mActiveModemCount = getTm().getActiveModemCount();
         mPhoneSubscriptions = new int[mActiveModemCount];
         mPhoneStates = new PhoneState[mActiveModemCount];
@@ -497,6 +503,12 @@
                                     @TelephonyManager.DataEnabledChangedReason int reason,
                                     @NonNull String callingPackage) {
                                 PhoneSwitcher.this.onDataEnabledChanged();
+                            }
+                            @Override
+                            public void onDataRoamingEnabledChanged(boolean enabled) {
+                                PhoneSwitcher.this.mAutoDataSwitchController.evaluateAutoDataSwitch(
+                                        AutoDataSwitchController
+                                                .EVALUATION_REASON_DATA_SETTINGS_CHANGED);
                             }});
                 phone.getDataSettingsManager().registerCallback(
                         mDataSettingsManagerCallbacks.get(phoneId));
@@ -513,7 +525,7 @@
         TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
                 context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
         telephonyRegistryManager.addOnSubscriptionsChangedListener(
-                mSubscriptionsChangedListener, mSubscriptionsChangedListener.getHandlerExecutor());
+                mSubscriptionsChangedListener, this::post);
 
         mConnectivityManager =
             (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -546,7 +558,7 @@
             }
         };
         mAutoDataSwitchController = new AutoDataSwitchController(context, looper, this,
-                mAutoDataSwitchCallback);
+                mFlags, mAutoDataSwitchCallback);
 
         mContext.registerReceiver(mDefaultDataChangedReceiver,
                 new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
@@ -901,6 +913,12 @@
                                 @NonNull String callingPackage) {
                             PhoneSwitcher.this.onDataEnabledChanged();
                         }
+                        @Override
+                        public void onDataRoamingEnabledChanged(boolean enabled) {
+                            PhoneSwitcher.this.mAutoDataSwitchController.evaluateAutoDataSwitch(
+                                    AutoDataSwitchController
+                                            .EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+                        }
                     });
             phone.getDataSettingsManager().registerCallback(
                     mDataSettingsManagerCallbacks.get(phone.getPhoneId()));
@@ -1079,6 +1097,7 @@
                     registerForImsRadioTechChange(mContext, i);
                 }
                 diffDetected = true;
+                mAutoDataSwitchController.notifySubscriptionsMappingChanged();
             }
         }
 
@@ -1387,7 +1406,7 @@
         return defaultDataPhone != null // check user enabled data
                 && defaultDataPhone.isUserDataEnabled()
                 && voicePhone != null // check user enabled voice during call feature
-                && voicePhone.isDataAllowed();
+                && voicePhone.getDataSettingsManager().isDataEnabled();
     }
 
     protected void transitionToEmergencyPhone() {
@@ -1502,18 +1521,22 @@
      */
     private void validate(int subId, boolean needValidation, int switchReason,
             @Nullable ISetOpportunisticDataCallback callback) {
-        logl("Validate subId " + subId + " due to " + switchReasonToString(switchReason)
-                + " needValidation=" + needValidation);
         int subIdToValidate = (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
                 ? mPrimaryDataSubId : subId;
+        logl("Validate subId " + subId + " due to " + switchReasonToString(switchReason)
+                + " needValidation=" + needValidation + " subIdToValidate=" + subIdToValidate
+                + " mAutoSelectedDataSubId=" + mAutoSelectedDataSubId
+                + " mPreferredDataSubId=" + mPreferredDataSubId.get());
         if (!isActiveSubId(subIdToValidate)) {
             logl("Can't switch data to inactive subId " + subIdToValidate);
             if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
                 // the default data sub is not selected yet, store the intent of switching to
                 // default subId once it becomes available.
                 mAutoSelectedDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+                sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
+            } else {
+                sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
             }
-            sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
             return;
         }
 
@@ -1545,7 +1568,7 @@
         // If validation feature is not supported, set it directly. Otherwise,
         // start validation on the subscription first.
         if (!mValidator.isValidationFeatureSupported()) {
-            setAutoSelectedDataSubIdInternal(subIdToValidate);
+            setAutoSelectedDataSubIdInternal(subId);
             sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
             return;
         }
@@ -1838,7 +1861,6 @@
         pw.println("mCurrentDdsSwitchFailure=" + mCurrentDdsSwitchFailure);
         pw.println("mLastSwitchPreferredDataReason="
                 + switchReasonToString(mLastSwitchPreferredDataReason));
-        pw.println("mDisplayedAutoSwitchNotification=" + mDisplayedAutoSwitchNotification);
         pw.println("Local logs:");
         pw.increaseIndent();
         mLocalLog.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
index 2cdf807..377c219 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.data;
 
+import android.annotation.NonNull;
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
 import android.net.NetworkRequest;
@@ -29,6 +30,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.NetworkRequestsStats;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
@@ -77,11 +79,22 @@
     @VisibleForTesting
     public final Handler mInternalHandler;
 
+    private final @NonNull FeatureFlags mFlags;
 
-    public TelephonyNetworkFactory(Looper looper, Phone phone) {
+
+    /**
+     * Constructor
+     *
+     * @param looper The looper for the handler
+     * @param phone The phone instance
+     * @param featureFlags The feature flags
+     */
+    public TelephonyNetworkFactory(@NonNull Looper looper, @NonNull Phone phone,
+            @NonNull FeatureFlags featureFlags) {
         super(looper, phone.getContext(), "TelephonyNetworkFactory[" + phone.getPhoneId()
                 + "]", null);
         mPhone = phone;
+        mFlags = featureFlags;
         mInternalHandler = new InternalHandler(looper);
 
         mAccessNetworksManager = mPhone.getAccessNetworksManager();
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
index b334b89..2668302 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
@@ -130,7 +130,9 @@
             new SimpleImmutableEntry<>(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY,
                     CAPABILITY_ATTRIBUTE_TRAFFIC_DESCRIPTOR_OS_APP_ID),
             new SimpleImmutableEntry<>(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
-                    CAPABILITY_ATTRIBUTE_TRAFFIC_DESCRIPTOR_OS_APP_ID)
+                    CAPABILITY_ATTRIBUTE_TRAFFIC_DESCRIPTOR_OS_APP_ID),
+            new SimpleImmutableEntry<>(NetworkCapabilities.NET_CAPABILITY_RCS,
+                CAPABILITY_ATTRIBUTE_APN_SETTING | CAPABILITY_ATTRIBUTE_TRAFFIC_DESCRIPTOR_DNN)
     );
 
     /** The phone instance. */
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
index 9a75b43..e3eed00 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
@@ -19,34 +19,40 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.AsyncResult;
-import android.os.CancellationSignal;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.DisconnectCause;
 import android.telephony.DomainSelectionService;
 import android.telephony.DomainSelectionService.EmergencyScanType;
 import android.telephony.DomainSelector;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.TransportSelectorCallback;
-import android.telephony.WwanSelectorCallback;
+import android.telephony.data.ApnSetting;
 import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorCallback;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.function.Consumer;
 
 
 /**
@@ -58,6 +64,18 @@
 
     protected static final int EVENT_EMERGENCY_NETWORK_SCAN_RESULT = 1;
     protected static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 2;
+    protected static final int EVENT_SERVICE_CONNECTED = 3;
+    protected static final int EVENT_SERVICE_BINDING_TIMEOUT = 4;
+    protected static final int EVENT_RESET_NETWORK_SCAN_DONE = 5;
+    protected static final int EVENT_LAST = EVENT_RESET_NETWORK_SCAN_DONE;
+
+    private static final int DEFAULT_BIND_RETRY_TIMEOUT_MS = 4 * 1000;
+
+    private static final int STATUS_DISPOSED         = 1 << 0;
+    private static final int STATUS_DOMAIN_SELECTED  = 1 << 1;
+    private static final int STATUS_WAIT_BINDING     = 1 << 2;
+    private static final int STATUS_WAIT_SCAN_RESULT = 1 << 3;
+    private static final int STATUS_WAIT_RESET_SCAN_RESULT = 1 << 4;
 
     /** Callback to receive responses from DomainSelectionConnection. */
     public interface DomainSelectionConnectionCallback {
@@ -70,72 +88,152 @@
         void onSelectionTerminated(@DisconnectCauses int cause);
     }
 
-    /** An internal class implementing {@link TransportSelectorCallback} interface. */
-    private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback {
+    private static class ScanRequest {
+        final int[] mPreferredNetworks;
+        final int mScanType;
+
+        ScanRequest(int[] preferredNetworks, int scanType) {
+            mPreferredNetworks = preferredNetworks;
+            mScanType = scanType;
+        }
+    }
+
+    /**
+     * A wrapper class for {@link ITransportSelectorCallback} interface.
+     */
+    private final class TransportSelectorCallbackAdaptor extends ITransportSelectorCallback.Stub {
         @Override
-        public void onCreated(@NonNull DomainSelector selector) {
-            mDomainSelector = selector;
-            DomainSelectionConnection.this.onCreated();
+        public void onCreated(@NonNull IDomainSelector selector) {
+            synchronized (mLock) {
+                mDomainSelector = selector;
+                if (checkState(STATUS_DISPOSED)) {
+                    try {
+                        selector.finishSelection();
+                    } catch (RemoteException e) {
+                        // ignore exception
+                    }
+                    return;
+                }
+                DomainSelectionConnection.this.onCreated();
+            }
         }
 
         @Override
         public void onWlanSelected(boolean useEmergencyPdn) {
-            DomainSelectionConnection.this.onWlanSelected(useEmergencyPdn);
+            synchronized (mLock) {
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
+                setState(STATUS_DOMAIN_SELECTED);
+                DomainSelectionConnection.this.onWlanSelected(useEmergencyPdn);
+            }
         }
 
         @Override
-        public @NonNull WwanSelectorCallback onWwanSelected() {
-            if (mWwanSelectorCallback == null) {
-                mWwanSelectorCallback = new WwanSelectorCallbackWrapper();
+        public void onWwanSelectedAsync(@NonNull final ITransportSelectorResultCallback cb) {
+            synchronized (mLock) {
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
+                if (mWwanSelectorCallback == null) {
+                    mWwanSelectorCallback = new WwanSelectorCallbackAdaptor();
+                }
+                if (mIsTestMode || !mIsEmergency
+                        || (mSelectorType != DomainSelectionService.SELECTOR_TYPE_CALLING)) {
+                    initHandler();
+                    mHandler.post(() -> {
+                        onWwanSelectedAsyncInternal(cb);
+                    });
+                } else {
+                    Thread workerThread = new Thread(new Runnable() {
+                        @Override
+                        public void run() {
+                            onWwanSelectedAsyncInternal(cb);
+                        }
+                    });
+                    workerThread.start();
+                }
+            }
+        }
+
+        private void onWwanSelectedAsyncInternal(
+                @NonNull final ITransportSelectorResultCallback cb) {
+            synchronized (mLock) {
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
             }
             DomainSelectionConnection.this.onWwanSelected();
-            return mWwanSelectorCallback;
-        }
-
-        @Override
-        public void onWwanSelected(final Consumer<WwanSelectorCallback> consumer) {
-            if (mWwanSelectorCallback == null) {
-                mWwanSelectorCallback = new WwanSelectorCallbackWrapper();
+            try {
+                cb.onCompleted(mWwanSelectorCallback);
+            } catch (RemoteException e) {
+                loge("onWwanSelectedAsync executor exception=" + e);
+                synchronized (mLock) {
+                    // Since remote service is not available,
+                    // wait for binding or timeout.
+                    waitForServiceBinding(null);
+                }
             }
-            if (mWwanSelectedExecutor == null) {
-                mWwanSelectedExecutor = Executors.newSingleThreadExecutor();
-            }
-            mWwanSelectedExecutor.execute(() -> {
-                DomainSelectionConnection.this.onWwanSelected();
-                consumer.accept(mWwanSelectorCallback);
-            });
         }
 
         @Override
         public void onSelectionTerminated(int cause) {
-            DomainSelectionConnection.this.onSelectionTerminated(cause);
-            dispose();
+            synchronized (mLock) {
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
+                DomainSelectionConnection.this.onSelectionTerminated(cause);
+                dispose();
+            }
         }
     }
 
-    /** An internal class implementing {@link WwanSelectorCallback} interface. */
-    private final class WwanSelectorCallbackWrapper
-            implements WwanSelectorCallback, CancellationSignal.OnCancelListener {
+    /**
+     * A wrapper class for {@link IWwanSelectorCallback} interface.
+     */
+    private final class WwanSelectorCallbackAdaptor extends IWwanSelectorCallback.Stub {
         @Override
-        public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
-                @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
-                @NonNull Consumer<EmergencyRegResult> consumer) {
-            if (signal != null) signal.setOnCancelListener(this);
-            mResultCallback = consumer;
-            initHandler();
-            DomainSelectionConnection.this.onRequestEmergencyNetworkScan(
-                    preferredNetworks.stream().mapToInt(Integer::intValue).toArray(), scanType);
+        public void onRequestEmergencyNetworkScan(
+                @NonNull @RadioAccessNetworkType int[] preferredNetworks,
+                @EmergencyScanType int scanType, boolean resetScan,
+                @NonNull IWwanSelectorResultCallback cb) {
+            synchronized (mLock) {
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
+                mResultCallback = cb;
+                initHandler();
+                mHandler.post(() -> {
+                    synchronized (mLock) {
+                        DomainSelectionConnection.this.onRequestEmergencyNetworkScan(
+                                preferredNetworks, scanType, resetScan);
+                    }
+                });
+            }
         }
 
         @Override
         public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain,
                 boolean useEmergencyPdn) {
-            DomainSelectionConnection.this.onDomainSelected(domain, useEmergencyPdn);
+            synchronized (mLock) {
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
+                setState(STATUS_DOMAIN_SELECTED);
+                DomainSelectionConnection.this.onDomainSelected(domain, useEmergencyPdn);
+            }
         }
 
         @Override
         public void onCancel() {
-            DomainSelectionConnection.this.onCancel();
+            synchronized (mLock) {
+                if (checkState(STATUS_DISPOSED) || mHandler == null) {
+                    return;
+                }
+                mHandler.post(() -> {
+                    DomainSelectionConnection.this.onCancel();
+                });
+            }
         }
     }
 
@@ -149,17 +247,60 @@
             AsyncResult ar;
             switch (msg.what) {
                 case EVENT_EMERGENCY_NETWORK_SCAN_RESULT:
-                    mIsWaitingForScanResult = false;
-                    if (mResultCallback == null) break;
                     ar = (AsyncResult) msg.obj;
-                    EmergencyRegResult regResult = (EmergencyRegResult) ar.result;
+                    EmergencyRegistrationResult regResult = (EmergencyRegistrationResult) ar.result;
                     if (DBG) logd("EVENT_EMERGENCY_NETWORK_SCAN_RESULT result=" + regResult);
-                    CompletableFuture.runAsync(
-                            () -> mResultCallback.accept(regResult),
-                            mController.getDomainSelectionServiceExecutor()).join();
+                    synchronized (mLock) {
+                        clearState(STATUS_WAIT_SCAN_RESULT);
+                        if (mResultCallback != null) {
+                            try {
+                                mResultCallback.onComplete(regResult);
+                            } catch (RemoteException e) {
+                                loge("EVENT_EMERGENCY_NETWORK_SCAN_RESULT exception=" + e);
+                                // Since remote service is not available,
+                                // wait for binding or timeout.
+                                waitForServiceBinding(null);
+                            }
+                        }
+                    }
                     break;
                 case EVENT_QUALIFIED_NETWORKS_CHANGED:
-                    onQualifiedNetworksChanged();
+                    ar = (AsyncResult) msg.obj;
+                    if (ar == null || ar.result == null) {
+                        loge("handleMessage EVENT_QUALIFIED_NETWORKS_CHANGED null result");
+                        break;
+                    }
+                    onQualifiedNetworksChanged((List<QualifiedNetworks>) ar.result);
+                    break;
+                case EVENT_SERVICE_CONNECTED:
+                    synchronized (mLock) {
+                        if (checkState(STATUS_DISPOSED) || !checkState(STATUS_WAIT_BINDING)) {
+                            loge("EVENT_SERVICE_CONNECTED disposed or not waiting for binding");
+                            break;
+                        }
+                        if (mController.selectDomain(mSelectionAttributes,
+                                mTransportSelectorCallback)) {
+                            clearWaitingForServiceBinding();
+                        }
+                    }
+                    break;
+                case EVENT_SERVICE_BINDING_TIMEOUT:
+                    synchronized (mLock) {
+                        if (!checkState(STATUS_DISPOSED) && checkState(STATUS_WAIT_BINDING)) {
+                            onServiceBindingTimeout();
+                        }
+                    }
+                    break;
+                case EVENT_RESET_NETWORK_SCAN_DONE:
+                    synchronized (mLock) {
+                        clearState(STATUS_WAIT_RESET_SCAN_RESULT);
+                        if (checkState(STATUS_DISPOSED)
+                                || (mPendingScanRequest == null)) {
+                            return;
+                        }
+                        onRequestEmergencyNetworkScan(mPendingScanRequest.mPreferredNetworks,
+                                mPendingScanRequest.mScanType, false);
+                    }
                     break;
                 default:
                     loge("handleMessage unexpected msg=" + msg.what);
@@ -170,8 +311,9 @@
 
     protected String mTag = "DomainSelectionConnection";
 
+    private final Object mLock = new Object();
     private final LocalLog mLocalLog = new LocalLog(30);
-    private final @NonNull TransportSelectorCallback mTransportSelectorCallback;
+    private final @NonNull ITransportSelectorCallback mTransportSelectorCallback;
 
     /**
      * Controls the communication between {@link DomainSelectionConnection} and
@@ -182,11 +324,14 @@
     private final boolean mIsEmergency;
 
     /** Interface to receive the request to trigger emergency network scan and selected domain. */
-    private @Nullable WwanSelectorCallback mWwanSelectorCallback;
+    private @Nullable IWwanSelectorCallback mWwanSelectorCallback;
     /** Interface to return the result of emergency network scan. */
-    private @Nullable Consumer<EmergencyRegResult> mResultCallback;
+    private @Nullable IWwanSelectorResultCallback mResultCallback;
     /** Interface to the {@link DomainSelector} created for this service. */
-    private @Nullable DomainSelector mDomainSelector;
+    private @Nullable IDomainSelector mDomainSelector;
+
+    /** The bit-wise OR of STATUS_* values. */
+    private int mStatus;
 
     /** The slot requested this connection. */
     protected @NonNull Phone mPhone;
@@ -196,14 +341,15 @@
     /** The attributes required to determine the domain. */
     private @Nullable DomainSelectionService.SelectionAttributes mSelectionAttributes;
 
-    private @Nullable Looper mLooper;
+    private final @NonNull Looper mLooper;
     protected @Nullable DomainSelectionConnectionHandler mHandler;
     private boolean mRegisteredRegistrant;
-    private boolean mIsWaitingForScanResult;
 
     private @NonNull AndroidFuture<Integer> mOnComplete;
 
-    private @Nullable Executor mWwanSelectedExecutor;
+    private @Nullable ScanRequest mPendingScanRequest;
+
+    private boolean mIsTestMode = false;
 
     /**
      * Creates an instance.
@@ -220,8 +366,9 @@
         mPhone = phone;
         mSelectorType = selectorType;
         mIsEmergency = isEmergency;
+        mLooper = Looper.getMainLooper();
 
-        mTransportSelectorCallback = new TransportSelectorCallbackWrapper();
+        mTransportSelectorCallback = new TransportSelectorCallbackAdaptor();
         mOnComplete = new AndroidFuture<>();
     }
 
@@ -235,16 +382,24 @@
     }
 
     /**
-     * Returns the interface for the callbacks.
+     * Returns the callback binder interface.
      *
-     * @return The {@link TransportSelectorCallback} interface.
+     * @return The {@link ITransportSelectorCallback} interface.
      */
-    @VisibleForTesting
-    public @NonNull TransportSelectorCallback getTransportSelectorCallback() {
+    public @Nullable ITransportSelectorCallback getTransportSelectorCallback() {
         return mTransportSelectorCallback;
     }
 
     /**
+     * Returns the callback binder interface to handle the emergency scan result.
+     *
+     * @return The {@link IWwanSelectorResultCallback} interface.
+     */
+    public @Nullable IWwanSelectorResultCallback getWwanSelectorResultCallback() {
+        return mResultCallback;
+    }
+
+    /**
      * Returns the {@link CompletableFuture} to receive the selected domain.
      *
      * @return The callback to receive response.
@@ -263,14 +418,20 @@
     }
 
     /**
-     * Requests the domain selection servic to select a domain.
+     * Requests the domain selection service to select a domain.
      *
      * @param attr The attributes required to determine the domain.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
     public void selectDomain(@NonNull DomainSelectionService.SelectionAttributes attr) {
-        mSelectionAttributes = attr;
-        mController.selectDomain(attr, getTransportSelectorCallback());
+        synchronized (mLock) {
+            mSelectionAttributes = attr;
+            if (mController.selectDomain(attr, mTransportSelectorCallback)) {
+                clearWaitingForServiceBinding();
+            } else {
+                waitForServiceBinding(attr);
+            }
+        }
     }
 
     /**
@@ -319,18 +480,54 @@
      *
      * @param preferredNetworks The ordered list of preferred networks to scan.
      * @param scanType Indicates the scan preference, such as full service or limited service.
+     * @param resetScan Indicates that the previous scan result shall be reset before scanning.
      */
     public void onRequestEmergencyNetworkScan(
             @NonNull @RadioAccessNetworkType int[] preferredNetworks,
-            @EmergencyScanType int scanType) {
+            @EmergencyScanType int scanType, boolean resetScan) {
         // Can be overridden if required
-        if (!mRegisteredRegistrant) {
-            mPhone.registerForEmergencyNetworkScan(mHandler,
-                    EVENT_EMERGENCY_NETWORK_SCAN_RESULT, null);
-            mRegisteredRegistrant = true;
+
+        synchronized (mLock) {
+            if (mHandler == null
+                    || checkState(STATUS_DISPOSED)
+                    || checkState(STATUS_WAIT_SCAN_RESULT)) {
+                logi("onRequestEmergencyNetworkScan waitResult="
+                        + checkState(STATUS_WAIT_SCAN_RESULT));
+                return;
+            }
+
+            if (checkState(STATUS_WAIT_RESET_SCAN_RESULT)) {
+                if (mPendingScanRequest != null) {
+                    /* Consecutive scan requests without cancellation is not an expected use case.
+                     * DomainSelector should cancel the previous request or wait for the result
+                     * before requesting a new scan.*/
+                    logi("onRequestEmergencyNetworkScan consecutive scan requests");
+                    return;
+                } else {
+                    // The reset has not been completed.
+                    // case1) Long delay in cancelEmergencyNetworkScan by modem.
+                    // case2) A consecutive scan requests with short interval from DomainSelector.
+                    logi("onRequestEmergencyNetworkScan reset not completed");
+                }
+                mPendingScanRequest = new ScanRequest(preferredNetworks, scanType);
+                return;
+            } else if (resetScan) {
+                setState(STATUS_WAIT_RESET_SCAN_RESULT);
+                mPendingScanRequest = new ScanRequest(preferredNetworks, scanType);
+                mPhone.cancelEmergencyNetworkScan(resetScan,
+                        mHandler.obtainMessage(EVENT_RESET_NETWORK_SCAN_DONE));
+                return;
+            }
+
+            if (!mRegisteredRegistrant) {
+                mPhone.registerForEmergencyNetworkScan(mHandler,
+                        EVENT_EMERGENCY_NETWORK_SCAN_RESULT, null);
+                mRegisteredRegistrant = true;
+            }
+            setState(STATUS_WAIT_SCAN_RESULT);
+            mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType, null);
+            mPendingScanRequest = null;
         }
-        mIsWaitingForScanResult = true;
-        mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType, null);
     }
 
     /**
@@ -365,8 +562,9 @@
     }
 
     private void onCancel(boolean resetScan) {
-        if (mIsWaitingForScanResult) {
-            mIsWaitingForScanResult = false;
+        mPendingScanRequest = null;
+        if (checkState(STATUS_WAIT_SCAN_RESULT)) {
+            clearState(STATUS_WAIT_SCAN_RESULT);
             mPhone.cancelEmergencyNetworkScan(resetScan, null);
         }
     }
@@ -376,9 +574,7 @@
      * to clean up all ongoing operations with the framework.
      */
     public void cancelSelection() {
-        if (mDomainSelector == null) return;
-        mDomainSelector.cancelSelection();
-        dispose();
+        finishSelection();
     }
 
     /**
@@ -389,53 +585,152 @@
      */
     public @NonNull CompletableFuture<Integer> reselectDomain(
             @NonNull DomainSelectionService.SelectionAttributes attr) {
-        mSelectionAttributes = attr;
-        if (mDomainSelector == null) return null;
-        mOnComplete = new AndroidFuture<>();
-        mDomainSelector.reselectDomain(attr);
-        return mOnComplete;
+        synchronized (mLock) {
+            mSelectionAttributes = attr;
+            mOnComplete = new AndroidFuture<>();
+            clearState(STATUS_DOMAIN_SELECTED);
+            try {
+                if (mDomainSelector == null) {
+                    // Service connection has been disconnected.
+                    mSelectionAttributes = getSelectionAttributesToRebindService();
+                    if (mController.selectDomain(mSelectionAttributes,
+                            mTransportSelectorCallback)) {
+                        clearWaitingForServiceBinding();
+                    } else {
+                        waitForServiceBinding(null);
+                    }
+                } else {
+                    mDomainSelector.reselectDomain(attr);
+                }
+            } catch (RemoteException e) {
+                loge("reselectDomain exception=" + e);
+                // Since remote service is not available, wait for binding or timeout.
+                waitForServiceBinding(null);
+            } finally {
+                return mOnComplete;
+            }
+        }
     }
 
     /**
      * Finishes the selection procedure and cleans everything up.
      */
     public void finishSelection() {
-        if (mDomainSelector == null) return;
-        mDomainSelector.finishSelection();
-        dispose();
+        synchronized (mLock) {
+            try {
+                if (mDomainSelector != null) {
+                    mDomainSelector.finishSelection();
+                }
+            } catch (RemoteException e) {
+                loge("finishSelection exception=" + e);
+            } finally {
+                dispose();
+            }
+        }
+    }
+
+    /** Indicates that the service connection has been connected. */
+    public void onServiceConnected() {
+        synchronized (mLock) {
+            if (checkState(STATUS_DISPOSED) || !checkState(STATUS_WAIT_BINDING)) {
+                logi("onServiceConnected disposed or not waiting for the binding");
+                return;
+            }
+            initHandler();
+            mHandler.sendEmptyMessage(EVENT_SERVICE_CONNECTED);
+        }
     }
 
     /** Indicates that the service connection has been removed. */
     public void onServiceDisconnected() {
-        // Can be overridden.
-        dispose();
+        synchronized (mLock) {
+            if (mHandler != null) {
+                mHandler.removeMessages(EVENT_SERVICE_CONNECTED);
+            }
+            if (checkState(STATUS_DISPOSED) || checkState(STATUS_DOMAIN_SELECTED)) {
+                // If there is an on-going dialing, recovery shall happen
+                // when dialing fails and reselectDomain() is called.
+                mDomainSelector = null;
+                mResultCallback = null;
+                return;
+            }
+            // Since remote service is not available, wait for binding or timeout.
+            waitForServiceBinding(null);
+        }
+    }
+
+    private void waitForServiceBinding(DomainSelectionService.SelectionAttributes attr) {
+        if (checkState(STATUS_DISPOSED) || checkState(STATUS_WAIT_BINDING)) {
+            // Already done.
+            return;
+        }
+        setState(STATUS_WAIT_BINDING);
+        mDomainSelector = null;
+        mResultCallback = null;
+        mSelectionAttributes = (attr != null) ? attr : getSelectionAttributesToRebindService();
+        initHandler();
+        mHandler.sendEmptyMessageDelayed(EVENT_SERVICE_BINDING_TIMEOUT,
+                DEFAULT_BIND_RETRY_TIMEOUT_MS);
+    }
+
+    private void clearWaitingForServiceBinding() {
+        if (checkState(STATUS_WAIT_BINDING)) {
+            clearState(STATUS_WAIT_BINDING);
+            if (mHandler != null) {
+                mHandler.removeMessages(EVENT_SERVICE_BINDING_TIMEOUT);
+            }
+        }
+    }
+
+    protected void onServiceBindingTimeout() {
+        // Can be overridden if required
+        synchronized (mLock) {
+            if (checkState(STATUS_DISPOSED)) {
+                logi("onServiceBindingTimeout disposed");
+                return;
+            }
+            DomainSelectionConnection.this.onSelectionTerminated(
+                    getTerminationCauseForSelectionTimeout());
+            dispose();
+        }
+    }
+
+    protected int getTerminationCauseForSelectionTimeout() {
+        // Can be overridden if required
+        return DisconnectCause.TIMED_OUT;
+    }
+
+    protected DomainSelectionService.SelectionAttributes
+            getSelectionAttributesToRebindService() {
+        // Can be overridden if required
+        return mSelectionAttributes;
+    }
+
+    /** Returns whether the client is waiting for the service binding. */
+    public boolean isWaitingForServiceBinding() {
+        return checkState(STATUS_WAIT_BINDING) && !checkState(STATUS_DISPOSED);
     }
 
     private void dispose() {
+        setState(STATUS_DISPOSED);
         if (mRegisteredRegistrant) {
             mPhone.unregisterForEmergencyNetworkScan(mHandler);
             mRegisteredRegistrant = false;
         }
         onCancel(true);
         mController.removeConnection(this);
-        if (mLooper != null) mLooper.quitSafely();
-        mLooper = null;
+        if (mHandler != null) mHandler.removeCallbacksAndMessages(null);
         mHandler = null;
     }
 
     protected void initHandler() {
-        if (mLooper == null) {
-            HandlerThread handlerThread = new HandlerThread(mTag);
-            handlerThread.start();
-            mLooper = handlerThread.getLooper();
-        }
         if (mHandler == null) mHandler = new DomainSelectionConnectionHandler(mLooper);
     }
 
     /**
      * Notifies the change of qualified networks.
      */
-    protected void onQualifiedNetworksChanged() {
+    protected void onQualifiedNetworksChanged(List<QualifiedNetworks> networksList) {
         if (mIsEmergency
                 && (mSelectorType == DomainSelectionService.SELECTOR_TYPE_CALLING)) {
             // DomainSelectionConnection for emergency calls shall override this.
@@ -445,6 +740,55 @@
     }
 
     /**
+     * Get the  preferred transport.
+     *
+     * @param apnType APN type.
+     * @return The preferred transport.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public int getPreferredTransport(@ApnType int apnType,
+            List<QualifiedNetworks> networksList) {
+        for (QualifiedNetworks networks : networksList) {
+            if (networks.qualifiedNetworks.length > 0) {
+                if (networks.apnType == apnType) {
+                    return getTransportFromAccessNetwork(networks.qualifiedNetworks[0]);
+                }
+            }
+        }
+
+        loge("getPreferredTransport no network found for " + ApnSetting.getApnTypeString(apnType));
+        return AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+    }
+
+    private static @TransportType int getTransportFromAccessNetwork(int accessNetwork) {
+        return accessNetwork == AccessNetworkType.IWLAN
+                ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+                : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+    }
+
+    private void setState(int stateBit) {
+        mStatus |= stateBit;
+    }
+
+    private void clearState(int stateBit) {
+        mStatus &= ~stateBit;
+    }
+
+    private boolean checkState(int stateBit) {
+        return (mStatus & stateBit) == stateBit;
+    }
+
+    /**
+     * Set whether it is unit test or not.
+     *
+     * @param testMode Indicates whether it is unit test or not.
+     */
+    @VisibleForTesting
+    public void setTestMode(boolean testMode) {
+        mIsTestMode = testMode;
+    }
+
+    /**
      * Dumps local log.
      */
     public void dump(@NonNull PrintWriter printWriter) {
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
index 52c9960..ee8517d 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
@@ -21,27 +21,33 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.telephony.BarringInfo;
 import android.telephony.DomainSelectionService;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
-import android.telephony.TransportSelectorCallback;
 import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.IDomainSelectionServiceController;
+import com.android.internal.telephony.ITransportSelectorCallback;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.concurrent.Executor;
 
 /**
  * Manages the connection to {@link DomainSelectionService}.
@@ -53,10 +59,30 @@
     private static final int EVENT_SERVICE_STATE_CHANGED = 1;
     private static final int EVENT_BARRING_INFO_CHANGED = 2;
 
+    private static final int BIND_START_DELAY_MS = 2 * 1000; // 2 seconds
+    private static final int BIND_MAXIMUM_DELAY_MS = 60 * 1000; // 1 minute
+
+    /**
+     * Returns the currently defined rebind retry timeout. Used for testing.
+     */
+    @VisibleForTesting
+    public interface BindRetry {
+        /**
+         * Returns a long in milliseconds indicating how long the DomainSelectionController
+         * should wait before rebinding for the first time.
+         */
+        long getStartDelay();
+
+        /**
+         * Returns a long in milliseconds indicating the maximum time the DomainSelectionController
+         * should wait before rebinding.
+         */
+        long getMaximumDelay();
+    }
+
     private final HandlerThread mHandlerThread =
             new HandlerThread("DomainSelectionControllerHandler");
 
-    private final DomainSelectionService mDomainSelectionService;
     private final Handler mHandler;
     // Only added or removed, never accessed on purpose.
     private final LocalLog mLocalLog = new LocalLog(30);
@@ -67,6 +93,123 @@
     protected final int[] mConnectionCounts;
     private final ArrayList<DomainSelectionConnection> mConnections = new ArrayList<>();
 
+    private ComponentName mComponentName;
+    private DomainSelectionServiceConnection mServiceConnection;
+    private IDomainSelectionServiceController mIServiceController;
+    // Binding the service is in progress or the service is bound already.
+    private boolean mIsBound = false;
+
+    private ExponentialBackoff mBackoff;
+    private boolean mBackoffStarted = false;
+    private boolean mUnbind = false;
+
+    // Retry the bind to the DomainSelectionService that has died after mBindRetry timeout.
+    private Runnable mRestartBindingRunnable = new Runnable() {
+        @Override
+        public void run() {
+            bind();
+        }
+    };
+
+    private BindRetry mBindRetry = new BindRetry() {
+        @Override
+        public long getStartDelay() {
+            return BIND_START_DELAY_MS;
+        }
+
+        @Override
+        public long getMaximumDelay() {
+            return BIND_MAXIMUM_DELAY_MS;
+        }
+    };
+
+    private class DomainSelectionServiceConnection implements ServiceConnection {
+        // Track the status of whether or not the Service has died in case we need to permanently
+        // unbind (see onNullBinding below).
+        private boolean mIsServiceConnectionDead = false;
+
+        /** {@inheritDoc} */
+        @Override
+        public void onServiceConnected(ComponentName unusedName, IBinder service) {
+            if (mHandler.getLooper().isCurrentThread()) {
+                onServiceConnectedInternal(service);
+            } else {
+                mHandler.post(() -> onServiceConnectedInternal(service));
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void onServiceDisconnected(ComponentName unusedName) {
+            if (mHandler.getLooper().isCurrentThread()) {
+                onServiceDisconnectedInternal();
+            } else {
+                mHandler.post(() -> onServiceDisconnectedInternal());
+            }
+        }
+
+        @Override
+        public void onBindingDied(ComponentName unusedName) {
+            if (mHandler.getLooper().isCurrentThread()) {
+                onBindingDiedInternal();
+            } else {
+                mHandler.post(() -> onBindingDiedInternal());
+            }
+        }
+
+        @Override
+        public void onNullBinding(ComponentName unusedName) {
+            if (mHandler.getLooper().isCurrentThread()) {
+                onNullBindingInternal();
+            } else {
+                mHandler.post(() -> onNullBindingInternal());
+            }
+        }
+
+        private void onServiceConnectedInternal(IBinder service) {
+            synchronized (mLock) {
+                stopBackoffTimer();
+                logi("onServiceConnected with binder: " + service);
+                setServiceController(service);
+            }
+            notifyServiceConnected();
+        }
+
+        private void onServiceDisconnectedInternal() {
+            synchronized (mLock) {
+                setServiceController(null);
+            }
+            logi("onServiceDisconnected");
+            notifyServiceDisconnected();
+        }
+
+        private void onBindingDiedInternal() {
+            mIsServiceConnectionDead = true;
+            synchronized (mLock) {
+                mIsBound = false;
+                setServiceController(null);
+                unbindService();
+                notifyBindFailure();
+            }
+            loge("onBindingDied starting retrying in "
+                    + mBackoff.getCurrentDelay() + " mS");
+            notifyServiceDisconnected();
+        }
+
+        private void onNullBindingInternal() {
+            loge("onNullBinding serviceDead=" + mIsServiceConnectionDead);
+            // onNullBinding will happen after onBindingDied. In this case, we should not
+            // permanently unbind and instead let the automatic rebind occur.
+            if (mIsServiceConnectionDead) return;
+            synchronized (mLock) {
+                mIsBound = false;
+                setServiceController(null);
+                unbindService();
+            }
+            notifyServiceDisconnected();
+        }
+    }
+
     private final class DomainSelectionControllerHandler extends Handler {
         DomainSelectionControllerHandler(Looper looper) {
             super(looper);
@@ -95,25 +238,22 @@
      * Creates an instance.
      *
      * @param context Context object from hosting application.
-     * @param service The {@link DomainSelectionService} instance.
      */
-    public DomainSelectionController(@NonNull Context context,
-            @NonNull DomainSelectionService service) {
-        this(context, service, null);
+    public DomainSelectionController(@NonNull Context context) {
+        this(context, null, null);
     }
 
     /**
      * Creates an instance.
      *
      * @param context Context object from hosting application.
-     * @param service The {@link DomainSelectionService} instance.
      * @param looper Handles event messages.
+     * @param bindRetry The {@link BindRetry} instance.
      */
     @VisibleForTesting
     public DomainSelectionController(@NonNull Context context,
-            @NonNull DomainSelectionService service, @Nullable Looper looper) {
+            @Nullable Looper looper, @Nullable BindRetry bindRetry) {
         mContext = context;
-        mDomainSelectionService = service;
 
         if (looper == null) {
             mHandlerThread.start();
@@ -121,6 +261,16 @@
         }
         mHandler = new DomainSelectionControllerHandler(looper);
 
+        if (bindRetry != null) {
+            mBindRetry = bindRetry;
+        }
+        mBackoff = new ExponentialBackoff(
+                mBindRetry.getStartDelay(),
+                mBindRetry.getMaximumDelay(),
+                2, /* multiplier */
+                mHandler,
+                mRestartBindingRunnable);
+
         int numPhones = TelephonyManager.getDefault().getActiveModemCount();
         mConnectionCounts = new int[numPhones];
         for (int i = 0; i < numPhones; i++) {
@@ -181,14 +331,24 @@
      *
      * @param attr Attributetes required to determine the domain.
      * @param callback A callback to receive the response.
+     * @return {@code true} if it requested successfully, otherwise {@code false}.
      */
-    public void selectDomain(@NonNull DomainSelectionService.SelectionAttributes attr,
-            @NonNull TransportSelectorCallback callback) {
-        if (attr == null || callback == null) return;
+    public boolean selectDomain(@NonNull DomainSelectionService.SelectionAttributes attr,
+            @NonNull ITransportSelectorCallback callback) {
+        if (attr == null) return false;
         if (DBG) logd("selectDomain");
 
-        Executor e = mDomainSelectionService.getCachedExecutor();
-        e.execute(() -> mDomainSelectionService.onDomainSelection(attr, callback));
+        synchronized (mLock) {
+            try  {
+                if (mIServiceController != null) {
+                    mIServiceController.selectDomain(attr, callback);
+                    return true;
+                }
+            } catch (RemoteException e) {
+                loge("selectDomain e=" + e);
+            }
+        }
+        return false;
     }
 
     /**
@@ -201,9 +361,16 @@
         if (phone == null || serviceState == null) return;
         if (DBG) logd("updateServiceState phoneId=" + phone.getPhoneId());
 
-        Executor e = mDomainSelectionService.getCachedExecutor();
-        e.execute(() -> mDomainSelectionService.onServiceStateUpdated(
-                phone.getPhoneId(), phone.getSubId(), serviceState));
+        synchronized (mLock) {
+            try  {
+                if (mIServiceController != null) {
+                    mIServiceController.updateServiceState(
+                            phone.getPhoneId(), phone.getSubId(), serviceState);
+                }
+            } catch (RemoteException e) {
+                loge("updateServiceState e=" + e);
+            }
+        }
     }
 
     /**
@@ -216,9 +383,16 @@
         if (phone == null || info == null) return;
         if (DBG) logd("updateBarringInfo phoneId=" + phone.getPhoneId());
 
-        Executor e = mDomainSelectionService.getCachedExecutor();
-        e.execute(() -> mDomainSelectionService.onBarringInfoUpdated(
-                phone.getPhoneId(), phone.getSubId(), info));
+        synchronized (mLock) {
+            try  {
+                if (mIServiceController != null) {
+                    mIServiceController.updateBarringInfo(
+                            phone.getPhoneId(), phone.getSubId(), info);
+                }
+            } catch (RemoteException e) {
+                loge("updateBarringInfo e=" + e);
+            }
+        }
     }
 
     /**
@@ -260,6 +434,19 @@
 
     /**
      * Notifies the {@link DomainSelectionConnection} instances registered
+     * of the service connection.
+     */
+    private void notifyServiceConnected() {
+        for (DomainSelectionConnection c : mConnections) {
+            c.onServiceConnected();
+            Phone phone = c.getPhone();
+            updateServiceState(phone, phone.getServiceStateTracker().getServiceState());
+            updateBarringInfo(phone, phone.mCi.getLastBarringInfo());
+        }
+    }
+
+    /**
+     * Notifies the {@link DomainSelectionConnection} instances registered
      * of the service disconnection.
      */
     private void notifyServiceDisconnected() {
@@ -269,11 +456,119 @@
     }
 
     /**
-     * Gets the {@link Executor} which executes methods of {@link DomainSelectionService.}
-     * @return {@link Executor} instance.
+     * Sets the binder interface to communicate with {@link domainSelectionService}.
      */
-    public @NonNull Executor getDomainSelectionServiceExecutor() {
-        return mDomainSelectionService.getCachedExecutor();
+    protected void setServiceController(@NonNull IBinder serviceController) {
+        mIServiceController = IDomainSelectionServiceController.Stub.asInterface(serviceController);
+    }
+
+    /**
+     * Sends request to bind to {@link DomainSelectionService}.
+     *
+     * @param componentName The {@link ComponentName} instance.
+     * @return {@code true} if the service is in the process of being bound, {@code false} if it
+     *         has failed.
+     */
+    public boolean bind(@NonNull ComponentName componentName) {
+        mComponentName = componentName;
+        mUnbind = false;
+        return bind();
+    }
+
+    private boolean bind() {
+        logd("bind isBindingOrBound=" + mIsBound);
+        synchronized (mLock) {
+            if (mUnbind) return false;
+            if (!mIsBound) {
+                mIsBound = true;
+                Intent serviceIntent = new Intent(DomainSelectionService.SERVICE_INTERFACE)
+                        .setComponent(mComponentName);
+                mServiceConnection = new DomainSelectionServiceConnection();
+                int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                        | Context.BIND_IMPORTANT;
+                logi("binding DomainSelectionService");
+                try {
+                    boolean bindSucceeded = mContext.bindService(serviceIntent,
+                            mServiceConnection, serviceFlags);
+                    if (!bindSucceeded) {
+                        loge("binding failed retrying in "
+                                + mBackoff.getCurrentDelay() + " mS");
+                        mIsBound = false;
+                        notifyBindFailure();
+                    }
+                    return bindSucceeded;
+                } catch (Exception e) {
+                    mIsBound = false;
+                    notifyBindFailure();
+                    loge("binding e=" + e.getMessage() + ", retrying in "
+                            + mBackoff.getCurrentDelay() + " mS");
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Unbinds the service.
+     */
+    public void unbind() {
+        synchronized (mLock) {
+            mUnbind = true;
+            stopBackoffTimer();
+            mIsBound = false;
+            setServiceController(null);
+            unbindService();
+        }
+    }
+
+    private void unbindService() {
+        synchronized (mLock) {
+            if (mServiceConnection != null) {
+                logi("unbinding Service");
+                mContext.unbindService(mServiceConnection);
+                mServiceConnection = null;
+            }
+        }
+    }
+
+    /**
+     * Gets the current delay to rebind service.
+     */
+    @VisibleForTesting
+    public long getBindDelay() {
+        return mBackoff.getCurrentDelay();
+    }
+
+    /**
+     * Stops backoff timer.
+     */
+    @VisibleForTesting
+    public void stopBackoffTimer() {
+        logi("stopBackoffTimer " + mBackoffStarted);
+        mBackoffStarted = false;
+        mBackoff.stop();
+    }
+
+    private void notifyBindFailure() {
+        logi("notifyBindFailure started=" + mBackoffStarted + ", unbind=" + mUnbind);
+        if (mUnbind) return;
+        if (mBackoffStarted) {
+            mBackoff.notifyFailed();
+        } else {
+            mBackoffStarted = true;
+            mBackoff.start();
+        }
+        logi("notifyBindFailure currentDelay=" + getBindDelay());
+    }
+
+    /**
+     * Returns the Handler instance.
+     */
+    @VisibleForTesting
+    public Handler getHandlerForTest() {
+        return mHandler;
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
index cbb74fa..410f89b 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
@@ -22,8 +22,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Context;
+import android.os.SystemProperties;
 import android.telephony.DomainSelectionService;
+import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 import android.util.Log;
@@ -31,6 +34,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -42,19 +47,27 @@
  * selector.
  */
 public class DomainSelectionResolver {
+    @VisibleForTesting
+    protected static final String PACKAGE_NAME_NONE = "none";
     private static final String TAG = DomainSelectionResolver.class.getSimpleName();
+    private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE;
+    /** For test purpose only with userdebug release */
+    private static final String PROP_DISABLE_DOMAIN_SELECTION =
+            "telephony.test.disable_domain_selection";
     private static DomainSelectionResolver sInstance = null;
 
     /**
      * Creates the DomainSelectionResolver singleton instance.
      *
      * @param context The context of the application.
-     * @param deviceConfigEnabled The flag to indicate whether or not the device supports
-     *                            the domain selection service or not.
+     * @param flattenedComponentName A flattened component name for the domain selection service
+     *                               to be bound to the domain selection controller.
      */
-    public static void make(Context context, boolean deviceConfigEnabled) {
+    public static void make(Context context, String flattenedComponentName) {
+        Log.i(TAG, "make flag=" + Flags.apDomainSelectionEnabled()
+                + ", useOem=" + Flags.useOemDomainSelectionService());
         if (sInstance == null) {
-            sInstance = new DomainSelectionResolver(context, deviceConfigEnabled);
+            sInstance = new DomainSelectionResolver(context, flattenedComponentName);
         }
     }
 
@@ -86,34 +99,33 @@
     @VisibleForTesting
     public interface DomainSelectionControllerFactory {
         /**
-         * Returns a {@link DomainSelectionController} created using the specified
-         * context and {@link DomainSelectionService} instance.
+         * Returns a {@link DomainSelectionController} created using the specified context.
          */
-        DomainSelectionController create(@NonNull Context context,
-                @NonNull DomainSelectionService service);
+        DomainSelectionController create(@NonNull Context context);
     }
 
     private DomainSelectionControllerFactory mDomainSelectionControllerFactory =
             new DomainSelectionControllerFactory() {
-        @Override
-        public DomainSelectionController create(@NonNull Context context,
-                @NonNull DomainSelectionService service) {
-            return new DomainSelectionController(context, service);
-        }
-    };
+                @Override
+                public DomainSelectionController create(@NonNull Context context) {
+                    return new DomainSelectionController(context);
+                }
+            };
 
     // Persistent Logging
     private final LocalLog mEventLog = new LocalLog(10);
     private final Context mContext;
-    // The flag to indicate whether the device supports the domain selection service or not.
-    private final boolean mDeviceConfigEnabled;
+    // Stores the default component name to bind the domain selection service so that
+    // the test can override this component name with their own domain selection service.
+    private final ComponentName mDefaultComponentName;
     // DomainSelectionController, which are bound to DomainSelectionService.
     private DomainSelectionController mController;
 
-    public DomainSelectionResolver(Context context, boolean deviceConfigEnabled) {
+    public DomainSelectionResolver(Context context, String flattenedComponentName) {
         mContext = context;
-        mDeviceConfigEnabled = deviceConfigEnabled;
-        logi("DomainSelectionResolver created: device-config=" + deviceConfigEnabled);
+        flattenedComponentName = (flattenedComponentName == null) ? "" : flattenedComponentName;
+        mDefaultComponentName = ComponentName.unflattenFromString(flattenedComponentName);
+        logi("DomainSelectionResolver created: componentName=[" + flattenedComponentName + "]");
     }
 
     /**
@@ -126,7 +138,11 @@
      *         {@code false} otherwise.
      */
     public boolean isDomainSelectionSupported() {
-        return mDeviceConfigEnabled && PhoneFactory.getDefaultPhone()
+        if (DBG && SystemProperties.getBoolean(PROP_DISABLE_DOMAIN_SELECTION, false)) {
+            logi("Disabled for test");
+            return false;
+        }
+        return mDefaultComponentName != null && PhoneFactory.getDefaultPhone()
                 .getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1);
     }
 
@@ -150,9 +166,14 @@
             throw new IllegalStateException("DomainSelection is not supported!");
         }
 
-        if (phone == null || !phone.isImsAvailable()) {
-            // If ImsPhone is null or the binder of ImsService is not available,
-            // CS domain is used for the telephony services.
+        if (phone == null || phone.getImsPhone() == null
+                || (!(isEmergency && selectorType == DomainSelectionService.SELECTOR_TYPE_CALLING)
+                        && !phone.isImsAvailable())) {
+            // In case of emergency calls, to recover the temporary failure in IMS service
+            // connection, DomainSelection shall be started even when IMS isn't available.
+            // DomainSelector will keep finding next available transport.
+            // For other telephony services, if the binder of ImsService is not available,
+            // CS domain will be used.
             return null;
         }
 
@@ -166,14 +187,60 @@
     }
 
     /**
-     * Needs to be called after the constructor to create a {@link DomainSelectionController} that
-     * is bound to the given {@link DomainSelectionService}.
-     *
-     * @param service A {@link DomainSelectionService} to be bound.
+     * Creates the {@link DomainSelectionController} and requests the domain selection controller
+     * to bind to the {@link DomainSelectionService} with the component name.
      */
-    public void initialize(@NonNull DomainSelectionService service) {
-        logi("Initialize.");
-        mController = mDomainSelectionControllerFactory.create(mContext, service);
+    public void initialize() {
+        logi("Initialize");
+        mController = mDomainSelectionControllerFactory.create(mContext);
+        if (mDefaultComponentName != null) {
+            mController.bind(mDefaultComponentName);
+        } else {
+            logi("No component name specified for domain selection service.");
+        }
+    }
+
+    /**
+     * Sets the component name of domain selection service to be bound.
+     *
+     * NOTE: This should only be used for testing.
+     *
+     * @return {@code true} if the requested operation is successfully done,
+     *         {@code false} otherwise.
+     */
+    public boolean setDomainSelectionServiceOverride(@NonNull ComponentName componentName) {
+        if (mController == null) {
+            logd("Controller is not initialized.");
+            return false;
+        }
+        logi("setDomainSelectionServiceOverride: " + componentName);
+        if (TextUtils.isEmpty(componentName.getPackageName())
+                || TextUtils.equals(PACKAGE_NAME_NONE, componentName.getPackageName())) {
+            // Unbind the active service connection to the domain selection service.
+            mController.unbind();
+            return true;
+        }
+        // Override the domain selection service with the given component name.
+        return mController.bind(componentName);
+    }
+
+    /**
+     * Clears the overridden domain selection service and restores the domain selection service
+     * with the default component.
+     *
+     * NOTE: This should only be used for testing.
+     *
+     * @return {@code true} if the requested operation is successfully done,
+     *         {@code false} otherwise.
+     */
+    public boolean clearDomainSelectionServiceOverride() {
+        if (mController == null) {
+            logd("Controller is not initialized.");
+            return false;
+        }
+        logi("clearDomainSelectionServiceOverride");
+        mController.unbind();
+        return mController.bind(mDefaultComponentName);
     }
 
     /**
@@ -200,6 +267,10 @@
         ipw.decreaseIndent();
     }
 
+    private void logd(String s) {
+        Log.d(TAG, s);
+    }
+
     private void logi(String s) {
         Log.i(TAG, s);
         mEventLog.log(s);
diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
index 5f3c3b6..e0e0354 100644
--- a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
@@ -28,11 +28,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.Uri;
+import android.telecom.PhoneAccount;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.Annotation.NetCapability;
 import android.telephony.DomainSelectionService;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
@@ -40,8 +43,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.data.AccessNetworksManager;
+import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
 
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -104,7 +109,7 @@
     /** {@inheritDoc} */
     @Override
     public void onWwanSelected() {
-        mEmergencyStateTracker.onEmergencyTransportChanged(
+        mEmergencyStateTracker.onEmergencyTransportChangedAndWait(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
     }
 
@@ -163,9 +168,8 @@
 
     /** {@inheritDoc} */
     @Override
-    protected void onQualifiedNetworksChanged() {
-        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
-        int preferredTransport = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY);
+    protected void onQualifiedNetworksChanged(List<QualifiedNetworks> networksList) {
+        int preferredTransport = getPreferredTransport(ApnSetting.TYPE_EMERGENCY, networksList);
         logi("onQualifiedNetworksChanged preferred=" + mPreferredTransportType
                 + ", current=" + preferredTransport);
         if (preferredTransport == mPreferredTransportType) {
@@ -177,6 +181,7 @@
                     future.complete(DOMAIN_PS);
                 }
             }
+            AccessNetworksManager anm = mPhone.getAccessNetworksManager();
             anm.unregisterForQualifiedNetworksChanged(mHandler);
         }
     }
@@ -198,6 +203,7 @@
      * @param exited {@code true} if the request caused the device to move out of airplane mode.
      * @param callId The call identifier.
      * @param number The dialed number.
+     * @param isTest Indicates it's a test emergency number.
      * @param callFailCause The reason why the last CS attempt failed.
      * @param imsReasonInfo The reason why the last PS attempt failed.
      * @param emergencyRegResult The current registration result for emergency services.
@@ -205,21 +211,44 @@
      */
     public static @NonNull DomainSelectionService.SelectionAttributes getSelectionAttributes(
             int slotId, int subId, boolean exited,
-            @NonNull String callId, @NonNull String number, int callFailCause,
-            @Nullable ImsReasonInfo imsReasonInfo,
-            @Nullable EmergencyRegResult emergencyRegResult) {
+            @NonNull String callId, @NonNull String number, boolean isTest,
+            int callFailCause, @Nullable ImsReasonInfo imsReasonInfo,
+            @Nullable EmergencyRegistrationResult emergencyRegResult) {
         DomainSelectionService.SelectionAttributes.Builder builder =
                 new DomainSelectionService.SelectionAttributes.Builder(
                         slotId, subId, SELECTOR_TYPE_CALLING)
                 .setEmergency(true)
+                .setTestEmergencyNumber(isTest)
                 .setExitedFromAirplaneMode(exited)
                 .setCallId(callId)
-                .setNumber(number)
+                .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null))
                 .setCsDisconnectCause(callFailCause);
 
         if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo);
-        if (emergencyRegResult != null) builder.setEmergencyRegResult(emergencyRegResult);
+        if (emergencyRegResult != null) builder.setEmergencyRegistrationResult(emergencyRegResult);
 
         return builder.build();
     }
+
+    @Override
+    protected DomainSelectionService.SelectionAttributes
+            getSelectionAttributesToRebindService() {
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes();
+        if (attr == null) return null;
+        DomainSelectionService.SelectionAttributes.Builder builder =
+                new DomainSelectionService.SelectionAttributes.Builder(
+                        attr.getSlotIndex(), attr.getSubscriptionId(), SELECTOR_TYPE_CALLING)
+                .setCallId(attr.getCallId())
+                .setAddress(attr.getAddress())
+                .setVideoCall(attr.isVideoCall())
+                .setEmergency(true)
+                .setTestEmergencyNumber(attr.isTestEmergencyNumber())
+                .setExitedFromAirplaneMode(attr.isExitedFromAirplaneMode())
+                .setEmergencyRegistrationResult(
+                        new EmergencyRegistrationResult(AccessNetworkType.UNKNOWN,
+                        NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
+                        NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0,
+                        "", "", ""));
+        return builder.build();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java
index efcdf11..b776e21 100644
--- a/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java
@@ -28,8 +28,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.data.AccessNetworksManager;
+import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
 
+import java.util.List;
+
 /**
  * Manages the information of request and the callback binder for an emergency SMS.
  */
@@ -139,14 +142,14 @@
     }
 
     @Override
-    protected void onQualifiedNetworksChanged() {
-        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
-        int preferredTransportType = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY);
+    protected void onQualifiedNetworksChanged(List<QualifiedNetworks> networksList) {
+        int preferredTransportType = getPreferredTransport(ApnSetting.TYPE_EMERGENCY, networksList);
 
         synchronized (mLock) {
             if (preferredTransportType == mPreferredTransportType) {
                 mPreferredTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
                 super.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true);
+                AccessNetworksManager anm = mPhone.getAccessNetworksManager();
                 anm.unregisterForQualifiedNetworksChanged(mHandler);
             }
         }
diff --git a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
index e157d24..0fd9201 100644
--- a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
@@ -20,11 +20,15 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.Uri;
+import android.telecom.PhoneAccount;
 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
 import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.DisconnectCause;
 import android.telephony.DomainSelectionService;
 import android.telephony.DomainSelectionService.EmergencyScanType;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PreciseDisconnectCause;
 import android.telephony.ims.ImsReasonInfo;
 
 import com.android.internal.telephony.Phone;
@@ -37,15 +41,9 @@
 public class NormalCallDomainSelectionConnection extends DomainSelectionConnection {
 
     private static final boolean DBG = false;
-
-    private static final String PREFIX_WPS = "*272";
-
-    // WPS prefix when CLIR is being activated for the call.
-    private static final String PREFIX_WPS_CLIR_ACTIVATE = "*31#*272";
-
-    // WPS prefix when CLIR is being deactivated for the call.
-    private static final String PREFIX_WPS_CLIR_DEACTIVATE = "#31#*272";
-
+    private int mDisconnectCause = DisconnectCause.NOT_VALID;
+    private int mPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID;
+    private String mReasonMessage = null;
 
     private @Nullable DomainSelectionConnectionCallback mCallback;
 
@@ -82,7 +80,7 @@
     /** {@inheritDoc} */
     @Override
     public void onRequestEmergencyNetworkScan(@RadioAccessNetworkType int[] preferredNetworks,
-            @EmergencyScanType int scanType) {
+            @EmergencyScanType int scanType, boolean resetScan) {
         // Not expected with normal calling.
         // Override to prevent abnormal behavior.
     }
@@ -123,7 +121,7 @@
                         slotId, subId, SELECTOR_TYPE_CALLING)
                         .setEmergency(false)
                         .setCallId(callId)
-                        .setNumber(number)
+                        .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null))
                         .setCsDisconnectCause(callFailCause)
                         .setVideoCall(isVideoCall);
 
@@ -134,13 +132,47 @@
     }
 
     /**
-     * Check if the call is Wireless Priority Service call
-     * @param dialString  The number being dialed.
-     * @return {@code true} if dialString matches WPS pattern and {@code false} otherwise.
+     * Save call disconnect info for error propagation.
+     * @param disconnectCause The code for the reason for the disconnect.
+     * @param preciseDisconnectCause The code for the precise reason for the disconnect.
+     * @param reasonMessage Description of the reason for the disconnect, not intended for the user
+     *                      to see.
      */
-    public static boolean isWpsCall(String dialString) {
-        return (dialString != null) && (dialString.startsWith(PREFIX_WPS)
-                || dialString.startsWith(PREFIX_WPS_CLIR_ACTIVATE)
-                || dialString.startsWith(PREFIX_WPS_CLIR_DEACTIVATE));
+    public void setDisconnectCause(int disconnectCause, int preciseDisconnectCause,
+                                String reasonMessage) {
+        mDisconnectCause = disconnectCause;
+        mPreciseDisconnectCause = preciseDisconnectCause;
+        mReasonMessage = reasonMessage;
+    }
+
+    public int getDisconnectCause() {
+        return mDisconnectCause;
+    }
+
+    public int getPreciseDisconnectCause() {
+        return mPreciseDisconnectCause;
+    }
+
+    public String getReasonMessage() {
+        return mReasonMessage;
+    }
+
+    /**
+     * @return imsReasonInfo Reason for the IMS call failure.
+     */
+    public @Nullable ImsReasonInfo getImsReasonInfo() {
+        if (getSelectionAttributes() == null) {
+            // Neither selectDomain(...) nor reselectDomain(...) has been called yet.
+            return null;
+        }
+
+        return getSelectionAttributes().getPsDisconnectCause();
+    }
+
+    /**
+     * @return phoneId To support localized message based on phoneId
+     */
+    public int getPhoneId() {
+        return getPhone().getPhoneId();
     }
 }
diff --git a/src/java/com/android/internal/telephony/domainselection/OWNERS b/src/java/com/android/internal/telephony/domainselection/OWNERS
new file mode 100644
index 0000000..2a76770
--- /dev/null
+++ b/src/java/com/android/internal/telephony/domainselection/OWNERS
@@ -0,0 +1,9 @@
+# automatically inherit owners from fw/opt/telephony
+
+hwangoo@google.com
+forestchoi@google.com
+avinashmp@google.com
+mkoon@google.com
+seheele@google.com
+radhikaagrawal@google.com
+jdyou@google.com
diff --git a/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
index 36a7b17..b3f4924 100644
--- a/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
@@ -53,17 +53,6 @@
         if (mCallback != null) mCallback.onSelectionTerminated(cause);
     }
 
-    @Override
-    public void finishSelection() {
-        CompletableFuture<Integer> future = getCompletableFuture();
-
-        if (future != null && !future.isDone()) {
-            cancelSelection();
-        } else {
-            super.finishSelection();
-        }
-    }
-
     /**
      * Requests a domain selection for SMS.
      *
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index 96cd880..a35cccf 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.emergency;
 
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL;
+
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_NONE;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
@@ -26,24 +28,30 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.sysprop.TelephonyProperties;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.DisconnectCauses;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
-import android.telephony.emergency.EmergencyNumber;
+import android.telephony.data.ApnSetting;
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -54,6 +62,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
@@ -62,6 +71,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -75,11 +85,16 @@
      * Timeout before we continue with the emergency call without waiting for DDS switch response
      * from the modem.
      */
-    private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000;
+    private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1 * 1000;
+    @VisibleForTesting
+    public static final int DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS = 3 * 1000;
     /** Default value for if Emergency Callback Mode is supported. */
     private static final boolean DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED = true;
     /** Default Emergency Callback Mode exit timeout value. */
     private static final long DEFAULT_ECM_EXIT_TIMEOUT_MS = 300000;
+    private static final int DEFAULT_EPDN_DISCONNECTION_TIMEOUT_MS = 500;
+
+    private static final int DEFAULT_TRANSPORT_CHANGE_TIMEOUT_MS = 1 * 1000;
 
     /** The emergency types used when setting the emergency mode on modem. */
     @Retention(RetentionPolicy.SOURCE)
@@ -94,6 +109,8 @@
     /** Indicates the emergency type is SMS. */
     public static final int EMERGENCY_TYPE_SMS = 2;
 
+    private static final String KEY_NO_SIM_ECBM_SUPPORT = "no_sim_ecbm_support";
+
     private static EmergencyStateTracker INSTANCE = null;
 
     private final Context mContext;
@@ -105,7 +122,7 @@
     @EmergencyConstants.EmergencyMode
     private int mEmergencyMode = MODE_EMERGENCY_NONE;
     private boolean mWasEmergencyModeSetOnModem;
-    private EmergencyRegResult mLastEmergencyRegResult;
+    private EmergencyRegistrationResult mLastEmergencyRegistrationResult;
     private boolean mIsEmergencyModeInProgress;
     private boolean mIsEmergencyCallStartedDuringEmergencySms;
 
@@ -114,10 +131,13 @@
     // A runnable which is used to automatically exit from Ecm after a period of time.
     private final Runnable mExitEcmRunnable = this::exitEmergencyCallbackMode;
     // Tracks emergency calls by callId that have reached {@link Call.State#ACTIVE}.
-    private final Set<String> mActiveEmergencyCalls = new ArraySet<>();
+    private final Set<android.telecom.Connection> mActiveEmergencyCalls = new ArraySet<>();
+    private Phone mPhoneToExit;
+    private int mPdnDisconnectionTimeoutMs = DEFAULT_EPDN_DISCONNECTION_TIMEOUT_MS;
+    private final Object mLock = new Object();
     private Phone mPhone;
-    // Tracks ongoing emergency callId to handle a second emergency call
-    private String mOngoingCallId;
+    // Tracks ongoing emergency connection to handle a second emergency call
+    private android.telecom.Connection mOngoingConnection;
     // Domain of the active emergency call. Assuming here that there will only be one domain active.
     private int mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN;
     private CompletableFuture<Integer> mCallEmergencyModeFuture;
@@ -125,6 +145,7 @@
     private boolean mIsInEcm;
     private boolean mIsTestEmergencyNumber;
     private Runnable mOnEcmExitCompleteRunnable;
+    private int mOngoingCallProperties;
 
     /** For emergency SMS */
     private final Set<String> mOngoingEmergencySmsIds = new ArraySet<>();
@@ -132,6 +153,14 @@
     private CompletableFuture<Integer> mSmsEmergencyModeFuture;
     private boolean mIsTestEmergencyNumberForSms;
 
+    private CompletableFuture<Boolean> mEmergencyTransportChangedFuture;
+
+    private final android.util.ArrayMap<Integer, Boolean> mNoSimEcbmSupported =
+            new android.util.ArrayMap<>();
+    private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
+            (slotIndex, subId, carrierId, specificCarrierId) -> onCarrierConfigurationChanged(
+                    slotIndex, subId);
+
     /**
      * Listens for Emergency Callback Mode state change intents
      */
@@ -153,6 +182,29 @@
         }
     };
 
+    /**
+     * TelephonyCallback used to monitor whether ePDN on cellular network is disconnected or not.
+     */
+    private final class PreciseDataConnectionStateListener extends TelephonyCallback implements
+            TelephonyCallback.PreciseDataConnectionStateListener {
+        @Override
+        public void onPreciseDataConnectionStateChanged(
+                @NonNull PreciseDataConnectionState dataConnectionState) {
+            ApnSetting apnSetting = dataConnectionState.getApnSetting();
+            if ((apnSetting == null)
+                    || ((apnSetting.getApnTypeBitmask() | ApnSetting.TYPE_EMERGENCY) == 0)
+                    || (dataConnectionState.getTransportType()
+                            != AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) {
+                return;
+            }
+            int state = dataConnectionState.getState();
+            Rlog.d(TAG, "onPreciseDataConnectionStateChanged ePDN state=" + state);
+            if (state == TelephonyManager.DATA_DISCONNECTED) exitEmergencyModeIfDelayed();
+        }
+    }
+
+    private PreciseDataConnectionStateListener mDataConnectionStateListener;
+
     /** PhoneFactory Dependencies for testing. */
     @VisibleForTesting
     public interface PhoneFactoryProxy {
@@ -176,6 +228,8 @@
     @VisibleForTesting
     public interface TelephonyManagerProxy {
         int getPhoneCount();
+        void registerTelephonyCallback(int subId, Executor executor, TelephonyCallback callback);
+        void unregisterTelephonyCallback(TelephonyCallback callback);
     }
 
     private final TelephonyManagerProxy mTelephonyManagerProxy;
@@ -183,7 +237,6 @@
     private static class TelephonyManagerProxyImpl implements TelephonyManagerProxy {
         private final TelephonyManager mTelephonyManager;
 
-
         TelephonyManagerProxyImpl(Context context) {
             mTelephonyManager = new TelephonyManager(context);
         }
@@ -192,6 +245,18 @@
         public int getPhoneCount() {
             return mTelephonyManager.getActiveModemCount();
         }
+
+        @Override
+        public void registerTelephonyCallback(int subId,
+                Executor executor, TelephonyCallback callback) {
+            TelephonyManager tm = mTelephonyManager.createForSubscriptionId(subId);
+            tm.registerTelephonyCallback(executor, callback);
+        }
+
+        @Override
+        public void unregisterTelephonyCallback(TelephonyCallback callback) {
+            mTelephonyManager.unregisterTelephonyCallback(callback);
+        }
     }
 
     /**
@@ -203,11 +268,15 @@
     }
 
     @VisibleForTesting
-    public static final int MSG_SET_EMERGENCY_MODE_DONE = 1;
+    public static final int MSG_SET_EMERGENCY_MODE = 1;
     @VisibleForTesting
-    public static final int MSG_EXIT_EMERGENCY_MODE_DONE = 2;
+    public static final int MSG_EXIT_EMERGENCY_MODE = 2;
     @VisibleForTesting
-    public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 3;
+    public static final int MSG_SET_EMERGENCY_MODE_DONE = 3;
+    @VisibleForTesting
+    public static final int MSG_EXIT_EMERGENCY_MODE_DONE = 4;
+    @VisibleForTesting
+    public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 5;
 
     private class MyHandler extends Handler {
 
@@ -224,14 +293,18 @@
                     Rlog.v(TAG, "MSG_SET_EMERGENCY_MODE_DONE for "
                             + emergencyTypeToString(emergencyType));
                     if (ar.exception == null) {
-                        mLastEmergencyRegResult = (EmergencyRegResult) ar.result;
+                        mLastEmergencyRegistrationResult = (EmergencyRegistrationResult) ar.result;
                     } else {
-                        mLastEmergencyRegResult = null;
-                        Rlog.w(TAG, "LastEmergencyRegResult not set. AsyncResult.exception: "
+                        mLastEmergencyRegistrationResult = null;
+                        Rlog.w(TAG,
+                                "LastEmergencyRegistrationResult not set. AsyncResult.exception: "
                                 + ar.exception);
                     }
                     setEmergencyModeInProgress(false);
 
+                    // Transport changed from WLAN to WWAN or CALLBACK to WWAN
+                    maybeNotifyTransportChangeCompleted(emergencyType, false);
+
                     if (emergencyType == EMERGENCY_TYPE_CALL) {
                         setIsInEmergencyCall(true);
                         completeEmergencyMode(emergencyType);
@@ -251,7 +324,7 @@
                             if (mIsEmergencyCallStartedDuringEmergencySms) {
                                 Phone phone = mPhone;
                                 mPhone = null;
-                                exitEmergencyMode(mSmsPhone, emergencyType);
+                                exitEmergencyMode(mSmsPhone, emergencyType, false);
                                 // Restore call phone for further use.
                                 mPhone = phone;
 
@@ -310,6 +383,27 @@
                     }
                     break;
                 }
+                case MSG_EXIT_EMERGENCY_MODE: {
+                    Rlog.v(TAG, "MSG_EXIT_EMERGENCY_MODE");
+                    exitEmergencyModeIfDelayed();
+                    break;
+                }
+                case MSG_SET_EMERGENCY_MODE: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Integer emergencyType = (Integer) ar.userObj;
+                    Rlog.v(TAG, "MSG_SET_EMERGENCY_MODE for "
+                            + emergencyTypeToString(emergencyType) + ", " + mEmergencyMode);
+                    // Should be reached here only when starting a new emergency service
+                    // while exiting emergency callback mode on the other slot.
+                    if (mEmergencyMode != MODE_EMERGENCY_WWAN) return;
+                    final Phone phone = (mPhone != null) ? mPhone : mSmsPhone;
+                    if (phone != null) {
+                        mWasEmergencyModeSetOnModem = true;
+                        phone.setEmergencyMode(MODE_EMERGENCY_WWAN,
+                                mHandler.obtainMessage(MSG_SET_EMERGENCY_MODE_DONE, emergencyType));
+                    }
+                    break;
+                }
                 default:
                     break;
             }
@@ -356,6 +450,13 @@
         mWakeLock = (pm != null) ? pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                 "telephony:" + TAG) : null;
         mConfigManager = context.getSystemService(CarrierConfigManager.class);
+        if (mConfigManager != null) {
+            // Carrier config changed callback should be executed in handler thread
+            mConfigManager.registerCarrierConfigChangeListener(mHandler::post,
+                    mCarrierConfigChangeListener);
+        } else {
+            Rlog.e(TAG, "CarrierConfigLoader is not available.");
+        }
 
         // Register receiver for ECM exit.
         IntentFilter filter = new IntentFilter();
@@ -392,6 +493,8 @@
         mEcmExitTimeoutMs = ecmExitTimeoutMs;
         mWakeLock = null; // Don't declare a wakelock in tests
         mConfigManager = context.getSystemService(CarrierConfigManager.class);
+        mConfigManager.registerCarrierConfigChangeListener(mHandler::post,
+                mCarrierConfigChangeListener);
         IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         context.registerReceiver(mEcmExitReceiver, filter, null, mHandler);
@@ -404,22 +507,34 @@
      * Handles turning on radio and switching DDS.
      *
      * @param phone                 the {@code Phone} on which to process the emergency call.
-     * @param callId                the call id on which to process the emergency call.
+     * @param c                     the {@code Connection} on which to process the emergency call.
      * @param isTestEmergencyNumber whether this is a test emergency number.
      * @return a {@code CompletableFuture} that results in {@code DisconnectCause.NOT_DISCONNECTED}
      *         if emergency call successfully started.
      */
     public CompletableFuture<Integer> startEmergencyCall(@NonNull Phone phone,
-            @NonNull String callId, boolean isTestEmergencyNumber) {
-        Rlog.i(TAG, "startEmergencyCall: phoneId=" + phone.getPhoneId() + ", callId=" + callId);
+            @NonNull android.telecom.Connection c, boolean isTestEmergencyNumber) {
+        Rlog.i(TAG, "startEmergencyCall: phoneId=" + phone.getPhoneId()
+                + ", callId=" + c.getTelecomCallId());
 
         if (mPhone != null) {
             // Create new future to return as to not interfere with any uncompleted futures.
             // Case1) When 2nd emergency call is initiated during an active call on the same phone.
             // Case2) While the device is in ECBM, an emergency call is initiated on the same phone.
             if (isSamePhone(mPhone, phone) && (!mActiveEmergencyCalls.isEmpty() || isInEcm())) {
-                mOngoingCallId = callId;
+                mOngoingConnection = c;
                 mIsTestEmergencyNumber = isTestEmergencyNumber;
+                // Ensure that domain selector requests scan.
+                mLastEmergencyRegistrationResult = new EmergencyRegistrationResult(
+                        AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+                        NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
+                        NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
+                if (isInEcm()) {
+                    // Remove pending exit ECM runnable.
+                    mHandler.removeCallbacks(mExitEcmRunnable);
+                    releaseWakeLock();
+                    ((GsmCdmaPhone) mPhone).notifyEcbmTimerReset(Boolean.TRUE);
+                }
                 return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
             }
 
@@ -427,6 +542,7 @@
             return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
         }
 
+        mOngoingCallProperties = 0;
         mCallEmergencyModeFuture = new CompletableFuture<>();
 
         if (mSmsPhone != null) {
@@ -437,17 +553,17 @@
             // exit the emergency mode when receiving the result of setting the emergency mode and
             // the emergency mode for this call will be restarted after the exit complete.
             if (isInEmergencyMode() && !isEmergencyModeInProgress()) {
-                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, false);
             }
 
             mPhone = phone;
-            mOngoingCallId = callId;
+            mOngoingConnection = c;
             mIsTestEmergencyNumber = isTestEmergencyNumber;
             return mCallEmergencyModeFuture;
         }
 
         mPhone = phone;
-        mOngoingCallId = callId;
+        mOngoingConnection = c;
         mIsTestEmergencyNumber = isTestEmergencyNumber;
         turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber);
         return mCallEmergencyModeFuture;
@@ -460,38 +576,41 @@
      * Enter ECM only once all active emergency calls have ended. If a call never reached
      * {@link Call.State#ACTIVE}, then no need to enter ECM.
      *
-     * @param callId the call id on which to end the emergency call.
+     * @param c the emergency call disconnected.
      */
-    public void endCall(@NonNull String callId) {
-        boolean wasActive = mActiveEmergencyCalls.remove(callId);
+    public void endCall(@NonNull android.telecom.Connection c) {
+        boolean wasActive = mActiveEmergencyCalls.remove(c);
 
-        if (Objects.equals(mOngoingCallId, callId)) {
-            mOngoingCallId = null;
+        if (Objects.equals(mOngoingConnection, c)) {
+            mOngoingConnection = null;
+            mOngoingCallProperties = 0;
         }
 
         if (wasActive && mActiveEmergencyCalls.isEmpty()
                 && isEmergencyCallbackModeSupported()) {
             enterEmergencyCallbackMode();
 
-            if (mOngoingCallId == null) {
+            if (mOngoingConnection == null) {
                 mIsEmergencyCallStartedDuringEmergencySms = false;
                 mCallEmergencyModeFuture = null;
             }
-        } else if (mOngoingCallId == null) {
+        } else if (mOngoingConnection == null) {
             if (isInEcm()) {
                 mIsEmergencyCallStartedDuringEmergencySms = false;
                 mCallEmergencyModeFuture = null;
                 // If the emergency call was initiated during the emergency callback mode,
                 // the emergency callback mode should be restored when the emergency call is ended.
                 if (mActiveEmergencyCalls.isEmpty()) {
-                    setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK,
-                            MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
+                    enterEmergencyCallbackMode();
                 }
             } else {
-                exitEmergencyMode(mPhone, EMERGENCY_TYPE_CALL);
+                exitEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, false);
                 clearEmergencyCallInfo();
             }
         }
+
+        // Release any blocked thread immediately
+        maybeNotifyTransportChangeCompleted(EMERGENCY_TYPE_CALL, true);
     }
 
     private void clearEmergencyCallInfo() {
@@ -499,7 +618,8 @@
         mIsTestEmergencyNumber = false;
         mIsEmergencyCallStartedDuringEmergencySms = false;
         mCallEmergencyModeFuture = null;
-        mOngoingCallId = null;
+        mOngoingConnection = null;
+        mOngoingCallProperties = 0;
         mPhone = null;
     }
 
@@ -511,10 +631,10 @@
                 Rlog.e(TAG, "DDS Switch failed.");
             }
             // Once radio is on and DDS switched, must call setEmergencyMode() before selecting
-            // emergency domain. EmergencyRegResult is required to determine domain and this is the
-            // only API that can receive it before starting domain selection. Once domain selection
-            // is finished, the actual emergency mode will be set when onEmergencyTransportChanged()
-            // is called.
+            // emergency domain. EmergencyRegistrationResult is required to determine domain and
+            // this is the only API that can receive it before starting domain selection.
+            // Once domain selection is finished, the actual emergency mode will be set when
+            // onEmergencyTransportChanged() is called.
             setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_WWAN,
                     MSG_SET_EMERGENCY_MODE_DONE);
         });
@@ -534,14 +654,15 @@
                 + emergencyTypeToString(emergencyType));
 
         if (mEmergencyMode == mode) {
+            // Initial transport selection of DomainSelector
+            maybeNotifyTransportChangeCompleted(emergencyType, false);
             return;
         }
         mEmergencyMode = mode;
         setEmergencyModeInProgress(true);
 
         Message m = mHandler.obtainMessage(msg, Integer.valueOf(emergencyType));
-        if ((mIsTestEmergencyNumber && emergencyType == EMERGENCY_TYPE_CALL)
-                || (mIsTestEmergencyNumberForSms && emergencyType == EMERGENCY_TYPE_SMS)) {
+        if (mIsTestEmergencyNumberForSms && emergencyType == EMERGENCY_TYPE_SMS) {
             Rlog.d(TAG, "TestEmergencyNumber for " + emergencyTypeToString(emergencyType)
                     + ": Skipping setting emergency mode on modem.");
             // Send back a response for the command, but with null information
@@ -552,8 +673,27 @@
             return;
         }
 
-        mWasEmergencyModeSetOnModem = true;
-        phone.setEmergencyMode(mode, m);
+        synchronized (mLock) {
+            unregisterForDataConnectionStateChanges();
+            if (mPhoneToExit != null) {
+                if (emergencyType != EMERGENCY_TYPE_CALL) {
+                    setIsInEmergencyCall(false);
+                }
+                mOnEcmExitCompleteRunnable = null;
+                if (mPhoneToExit != phone) {
+                    // Exit emergency mode on the other phone first,
+                    // then set emergency mode on the given phone.
+                    mPhoneToExit.exitEmergencyMode(
+                            mHandler.obtainMessage(MSG_SET_EMERGENCY_MODE,
+                            Integer.valueOf(emergencyType)));
+                    mPhoneToExit = null;
+                    return;
+                }
+                mPhoneToExit = null;
+            }
+            mWasEmergencyModeSetOnModem = true;
+            phone.setEmergencyMode(mode, m);
+        }
     }
 
     private void completeEmergencyMode(@EmergencyType int emergencyType) {
@@ -626,8 +766,10 @@
      *
      * @param phone the {@code Phone} to exit the emergency mode.
      * @param emergencyType the emergency type to identify an emergency call or SMS.
+     * @param waitForPdnDisconnect indicates whether it shall wait for the disconnection of ePDN.
      */
-    private void exitEmergencyMode(Phone phone, @EmergencyType int emergencyType) {
+    private void exitEmergencyMode(Phone phone, @EmergencyType int emergencyType,
+            boolean waitForPdnDisconnect) {
         Rlog.i(TAG, "exitEmergencyMode for " + emergencyTypeToString(emergencyType));
 
         if (emergencyType == EMERGENCY_TYPE_CALL) {
@@ -663,13 +805,91 @@
             return;
         }
 
-        mWasEmergencyModeSetOnModem = false;
-        phone.exitEmergencyMode(m);
+        synchronized (mLock) {
+            mWasEmergencyModeSetOnModem = false;
+            if (waitForPdnDisconnect) {
+                registerForDataConnectionStateChanges(phone);
+                mPhoneToExit = phone;
+                if (mPdnDisconnectionTimeoutMs > 0) {
+                    // To avoid waiting for the disconnection indefinitely.
+                    mHandler.sendEmptyMessageDelayed(MSG_EXIT_EMERGENCY_MODE,
+                            mPdnDisconnectionTimeoutMs);
+                }
+                return;
+            } else {
+                unregisterForDataConnectionStateChanges();
+                mPhoneToExit = null;
+            }
+            phone.exitEmergencyMode(m);
+        }
     }
 
-    /** Returns last {@link EmergencyRegResult} as set by {@code setEmergencyMode()}. */
-    public EmergencyRegResult getEmergencyRegResult() {
-        return mLastEmergencyRegResult;
+    /** Returns last {@link EmergencyRegistrationResult} as set by {@code setEmergencyMode()}. */
+    public EmergencyRegistrationResult getEmergencyRegistrationResult() {
+        return mLastEmergencyRegistrationResult;
+    }
+
+    private void waitForTransportChangeCompleted(CompletableFuture<Boolean> future) {
+        if (future != null) {
+            synchronized (future) {
+                if ((mEmergencyMode == MODE_EMERGENCY_NONE)
+                        || mHandler.getLooper().isCurrentThread()) {
+                    // Do not block the Handler's thread
+                    return;
+                }
+                long now = SystemClock.elapsedRealtime();
+                long deadline = now + DEFAULT_TRANSPORT_CHANGE_TIMEOUT_MS;
+                // Guard with while loop to handle spurious wakeups
+                while (!future.isDone() && now < deadline) {
+                    try {
+                        future.wait(deadline - now);
+                    } catch (Exception e) {
+                        Rlog.e(TAG, "waitForTransportChangeCompleted wait e=" + e);
+                    }
+                    now = SystemClock.elapsedRealtime();
+                }
+            }
+        }
+    }
+
+    private void maybeNotifyTransportChangeCompleted(@EmergencyType int emergencyType,
+            boolean enforced) {
+        if (emergencyType != EMERGENCY_TYPE_CALL) {
+            // It's not for the emergency call
+            return;
+        }
+        CompletableFuture<Boolean> future = mEmergencyTransportChangedFuture;
+        if (future != null) {
+            synchronized (future) {
+                if (!future.isDone()
+                        && ((!isEmergencyModeInProgress() && mEmergencyMode == MODE_EMERGENCY_WWAN)
+                                || enforced)) {
+                    future.complete(Boolean.TRUE);
+                    future.notifyAll();
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles emergency transport change by setting new emergency mode.
+     *
+     * @param emergencyType the emergency type to identify an emergency call or SMS
+     * @param mode the new emergency mode
+     */
+    public void onEmergencyTransportChangedAndWait(@EmergencyType int emergencyType,
+            @EmergencyConstants.EmergencyMode int mode) {
+        // Wait for the completion of setting MODE_EMERGENCY_WWAN only for emergency calls
+        if (emergencyType == EMERGENCY_TYPE_CALL && mode == MODE_EMERGENCY_WWAN) {
+            CompletableFuture<Boolean> future = new CompletableFuture<>();
+            synchronized (future) {
+                mEmergencyTransportChangedFuture = future;
+                onEmergencyTransportChanged(emergencyType, mode);
+                waitForTransportChangeCompleted(future);
+            }
+            return;
+        }
+        onEmergencyTransportChanged(emergencyType, mode);
     }
 
     /**
@@ -701,10 +921,10 @@
     /**
      * Notify the tracker that the emergency call domain has been updated.
      * @param phoneType The new PHONE_TYPE_* of the call.
-     * @param callId The ID of the call
+     * @param c The connection of the call
      */
-    public void onEmergencyCallDomainUpdated(int phoneType, String callId) {
-        Rlog.d(TAG, "domain update for callId: " + callId);
+    public void onEmergencyCallDomainUpdated(int phoneType, android.telecom.Connection c) {
+        Rlog.d(TAG, "domain update for callId: " + c.getTelecomCallId());
         int domain = -1;
         switch(phoneType) {
             case (PhoneConstants.PHONE_TYPE_CDMA_LTE):
@@ -732,21 +952,81 @@
      * Handles emergency call state change.
      *
      * @param state the new call state
-     * @param callId the callId whose state has changed
+     * @param c the call whose state has changed
      */
-    public void onEmergencyCallStateChanged(Call.State state, String callId) {
+    public void onEmergencyCallStateChanged(Call.State state, android.telecom.Connection c) {
         if (state == Call.State.ACTIVE) {
-            mActiveEmergencyCalls.add(callId);
+            mActiveEmergencyCalls.add(c);
+            if (Objects.equals(mOngoingConnection, c)) {
+                Rlog.i(TAG, "call connected " + c.getTelecomCallId());
+                if (mPhone != null
+                        && isVoWiFi(mOngoingCallProperties)
+                        && mEmergencyMode == EmergencyConstants.MODE_EMERGENCY_WLAN) {
+                    // Recover normal service in cellular when VoWiFi is connected
+                    mPhone.cancelEmergencyNetworkScan(true, null);
+                }
+            }
         }
     }
 
     /**
+     * Handles the change of emergency call properties.
+     *
+     * @param properties the new call properties.
+     * @param c the call whose state has changed.
+     */
+    public void onEmergencyCallPropertiesChanged(int properties, android.telecom.Connection c) {
+        if (Objects.equals(mOngoingConnection, c)) {
+            mOngoingCallProperties = properties;
+        }
+    }
+
+    /**
+     * Handles the radio power off request.
+     */
+    public void onCellularRadioPowerOffRequested() {
+        synchronized (mLock) {
+            if (isInEcm()) {
+                exitEmergencyCallbackMode(null);
+            }
+            exitEmergencyModeIfDelayed();
+        }
+    }
+
+    private static boolean isVoWiFi(int properties) {
+        return (properties & android.telecom.Connection.PROPERTY_WIFI) > 0
+                || (properties & android.telecom.Connection.PROPERTY_CROSS_SIM) > 0;
+    }
+
+    /**
      * Returns {@code true} if device and carrier support emergency callback mode.
      */
-    private boolean isEmergencyCallbackModeSupported() {
-        return getConfig(mPhone.getSubId(),
-                CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL,
-                DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED);
+    @VisibleForTesting
+    public boolean isEmergencyCallbackModeSupported() {
+        int subId = mPhone.getSubId();
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            // If there is no SIM, refer to the saved last carrier configuration with valid
+            // subscription.
+            int phoneId = mPhone.getPhoneId();
+            Boolean savedConfig = mNoSimEcbmSupported.get(Integer.valueOf(phoneId));
+            if (savedConfig == null) {
+                // Exceptional case such as with poor boot performance.
+                // Usually, the first carrier config change will update the cache.
+                // But with poor boot performance, the carrier config change
+                // can be delayed for a long time.
+                SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+                savedConfig = Boolean.valueOf(
+                        sp.getBoolean(KEY_NO_SIM_ECBM_SUPPORT + phoneId, false));
+                Rlog.i(TAG, "ECBM value not cached, load from preference");
+                mNoSimEcbmSupported.put(Integer.valueOf(phoneId), savedConfig);
+            }
+            Rlog.i(TAG, "isEmergencyCallbackModeSupported savedConfig=" + savedConfig);
+            return savedConfig;
+        } else {
+            return getConfig(subId,
+                    CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL,
+                    DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED);
+        }
     }
 
     /**
@@ -769,20 +1049,23 @@
                 // ECBM (see ImsPhone#handleEnterEmergencyCallbackMode)
                 ((GsmCdmaPhone) mPhone).notifyEmergencyCallRegistrants(true);
             }
-
-            // Set emergency mode on modem.
-            setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK,
-                    MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
-
-            // Post this runnable so we will automatically exit if no one invokes
-            // exitEmergencyCallbackMode() directly.
-            long delayInMillis = TelephonyProperties.ecm_exit_timer()
-                    .orElse(mEcmExitTimeoutMs);
-            mHandler.postDelayed(mExitEcmRunnable, delayInMillis);
-
-            // We don't want to go to sleep while in ECM.
-            if (mWakeLock != null) mWakeLock.acquire(delayInMillis);
+        } else {
+            // Inform to reset the ECBM timer.
+            ((GsmCdmaPhone) mPhone).notifyEcbmTimerReset(Boolean.FALSE);
         }
+
+        // Set emergency mode on modem.
+        setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK,
+                MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
+
+        // Post this runnable so we will automatically exit if no one invokes
+        // exitEmergencyCallbackMode() directly.
+        long delayInMillis = TelephonyProperties.ecm_exit_timer()
+                .orElse(mEcmExitTimeoutMs);
+        mHandler.postDelayed(mExitEcmRunnable, delayInMillis);
+
+        // We don't want to go to sleep while in ECM.
+        if (mWakeLock != null) mWakeLock.acquire(delayInMillis);
     }
 
     /**
@@ -800,14 +1083,7 @@
             }
 
             // Release wakeLock.
-            if (mWakeLock != null && mWakeLock.isHeld()) {
-                try {
-                    mWakeLock.release();
-                } catch (Exception e) {
-                    // Ignore the exception if the system has already released this WakeLock.
-                    Rlog.d(TAG, "WakeLock already released: " + e.toString());
-                }
-            }
+            releaseWakeLock();
 
             GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) mPhone;
             // Send intents that ECM has changed.
@@ -815,7 +1091,9 @@
             gsmCdmaPhone.notifyEmergencyCallRegistrants(false);
 
             // Exit emergency mode on modem.
-            exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL);
+            // b/299866883: Wait for the disconnection of ePDN before calling exitEmergencyMode.
+            exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL,
+                    mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_PS);
         }
 
         mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN;
@@ -823,6 +1101,18 @@
         mPhone = null;
     }
 
+    private void releaseWakeLock() {
+        // Release wakeLock.
+        if (mWakeLock != null && mWakeLock.isHeld()) {
+            try {
+                mWakeLock.release();
+            } catch (Exception e) {
+                // Ignore the exception if the system has already released this WakeLock.
+                Rlog.d(TAG, "WakeLock already released: " + e.toString());
+            }
+        }
+    }
+
     /**
      * Exits emergency callback mode and triggers runnable after exit response is received.
      */
@@ -938,9 +1228,10 @@
      * This should be called once an emergency SMS is sent.
      *
      * @param smsId the SMS id on which to end the emergency SMS.
-     * @param emergencyNumber the emergency number which was used for the emergency SMS.
+     * @param success the flag specifying whether an emergency SMS is successfully sent or not.
+     *                {@code true} if SMS is successfully sent, {@code false} otherwise.
      */
-    public void endSms(@NonNull String smsId, EmergencyNumber emergencyNumber) {
+    public void endSms(@NonNull String smsId, boolean success) {
         mOngoingEmergencySmsIds.remove(smsId);
 
         // If the outgoing emergency SMSs are empty, we can try to exit the emergency mode.
@@ -948,12 +1239,12 @@
             if (isInEcm()) {
                 // When the emergency mode is not in MODE_EMERGENCY_CALLBACK,
                 // it needs to notify the emergency callback mode to modem.
-                if (mActiveEmergencyCalls.isEmpty() && mOngoingCallId == null) {
+                if (mActiveEmergencyCalls.isEmpty() && mOngoingConnection == null) {
                     setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK,
                             MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
                 }
             } else {
-                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, false);
             }
 
             clearEmergencySmsInfo();
@@ -991,8 +1282,8 @@
      *
      * <p>
      * Once radio is on and DDS switched, must call setEmergencyMode() before completing the future
-     * and selecting emergency domain. EmergencyRegResult is required to determine domain and
-     * setEmergencyMode() is the only API that can receive it before starting domain selection.
+     * and selecting emergency domain. EmergencyRegistrationResult is required to determine domain
+     * and setEmergencyMode() is the only API that can receive it before starting domain selection.
      * Once domain selection is finished, the actual emergency mode will be set when
      * onEmergencyTransportChanged() is called.
      *
@@ -1005,40 +1296,63 @@
             boolean isTestEmergencyNumber) {
         final boolean isAirplaneModeOn = isAirplaneModeOn(mContext);
         boolean needToTurnOnRadio = !isRadioOn() || isAirplaneModeOn;
+        final SatelliteController satelliteController = SatelliteController.getInstance();
+        boolean needToTurnOffSatellite = satelliteController.isSatelliteEnabled();
 
-        if (needToTurnOnRadio) {
+        if (needToTurnOnRadio || needToTurnOffSatellite) {
             Rlog.i(TAG, "turnOnRadioAndSwitchDds: phoneId=" + phone.getPhoneId() + " for "
                     + emergencyTypeToString(emergencyType));
             if (mRadioOnHelper == null) {
                 mRadioOnHelper = new RadioOnHelper(mContext);
             }
 
+            final Phone phoneForEmergency = phone;
+            final android.telecom.Connection expectedConnection = mOngoingConnection;
+            final int waitForInServiceTimeout =
+                    needToTurnOnRadio ? DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS : 0;
+            Rlog.i(TAG, "turnOnRadioAndSwitchDds: timeout=" + waitForInServiceTimeout);
             mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
                 @Override
                 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
                     if (!isRadioReady) {
-                        // Could not turn radio on
-                        Rlog.e(TAG, "Failed to turn on radio.");
-                        completeEmergencyMode(emergencyType, DisconnectCause.POWER_OFF);
+                        if (satelliteController.isSatelliteEnabled()) {
+                            // Could not turn satellite off
+                            Rlog.e(TAG, "Failed to turn off satellite modem.");
+                            completeEmergencyMode(emergencyType, DisconnectCause.SATELLITE_ENABLED);
+                        } else {
+                            // Could not turn radio on
+                            Rlog.e(TAG, "Failed to turn on radio.");
+                            completeEmergencyMode(emergencyType, DisconnectCause.POWER_OFF);
+                        }
                     } else {
+                        if (!Objects.equals(mOngoingConnection, expectedConnection)) {
+                            Rlog.i(TAG, "onComplete "
+                                    + expectedConnection.getTelecomCallId() + " canceled.");
+                            return;
+                        }
                         switchDdsAndSetEmergencyMode(phone, emergencyType);
                     }
                 }
 
                 @Override
                 public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
-                    // We currently only look to make sure that the radio is on before dialing. We
-                    // should be able to make emergency calls at any time after the radio has been
-                    // powered on and isn't in the UNAVAILABLE state, even if it is reporting the
-                    // OUT_OF_SERVICE state.
-                    return phone.getServiceStateTracker().isRadioOn();
+                    // Wait for normal service state or timeout if required.
+                    if (phone == phoneForEmergency
+                            && waitForInServiceTimeout > 0
+                            && !isNetworkRegistered(phone)) {
+                        return false;
+                    }
+                    return phone.getServiceStateTracker().isRadioOn()
+                            && !satelliteController.isSatelliteEnabled();
                 }
 
                 @Override
                 public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
-                    return true;
+                    // onTimeout shall be called only with the Phone for emergency
+                    return phone.getServiceStateTracker().isRadioOn()
+                            && !satelliteController.isSatelliteEnabled();
                 }
-            }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, 0);
+            }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, waitForInServiceTimeout);
         } else {
             switchDdsAndSetEmergencyMode(phone, emergencyType);
         }
@@ -1191,6 +1505,27 @@
                 || phone.getServiceState().isEmergencyOnly();
     }
 
+    private static boolean isNetworkRegistered(Phone phone) {
+        ServiceState ss = phone.getServiceStateTracker().getServiceState();
+        if (ss != null) {
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_PS,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri != null && nri.isNetworkRegistered()) {
+                // PS is IN_SERVICE state.
+                return true;
+            }
+            nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_CS,
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri != null && nri.isNetworkRegistered()) {
+                // CS is IN_SERVICE state.
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Checks whether both {@code Phone}s are same or not.
      */
@@ -1205,4 +1540,105 @@
             default: return "UNKNOWN";
         }
     }
+
+    private void onCarrierConfigurationChanged(int slotIndex, int subId) {
+        Rlog.i(TAG, "onCarrierConfigChanged slotIndex=" + slotIndex + ", subId=" + subId);
+
+        if (slotIndex < 0) {
+            return;
+        }
+
+        updateNoSimEcbmSupported(slotIndex, subId);
+    }
+
+    private void updateNoSimEcbmSupported(int slotIndex, int subId) {
+        SharedPreferences sp = null;
+        Boolean savedConfig = mNoSimEcbmSupported.get(Integer.valueOf(slotIndex));
+        if (savedConfig == null) {
+            sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+            savedConfig = Boolean.valueOf(
+                    sp.getBoolean(KEY_NO_SIM_ECBM_SUPPORT + slotIndex, false));
+            mNoSimEcbmSupported.put(Integer.valueOf(slotIndex), savedConfig);
+            Rlog.i(TAG, "updateNoSimEcbmSupported load from preference slotIndex=" + slotIndex
+                    + ", supported=" + savedConfig);
+        }
+
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            // invalid subId
+            return;
+        }
+
+        PersistableBundle b = getConfigBundle(subId, KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL);
+        if (b.isEmpty()) {
+            Rlog.e(TAG, "updateNoSimEcbmSupported empty result");
+            return;
+        }
+
+        if (!CarrierConfigManager.isConfigForIdentifiedCarrier(b)) {
+            Rlog.i(TAG, "updateNoSimEcbmSupported not carrier specific configuration");
+            return;
+        }
+
+        boolean carrierConfig = b.getBoolean(KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL);
+        if (carrierConfig == savedConfig) {
+            return;
+        }
+
+        mNoSimEcbmSupported.put(Integer.valueOf(slotIndex), Boolean.valueOf(carrierConfig));
+
+        if (sp == null) {
+            sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+        }
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putBoolean(KEY_NO_SIM_ECBM_SUPPORT + slotIndex, carrierConfig);
+        editor.apply();
+
+        Rlog.i(TAG, "updateNoSimEcbmSupported preference updated slotIndex=" + slotIndex
+                + ", supported=" + carrierConfig);
+    }
+
+    /** For test purpose only */
+    @VisibleForTesting
+    public void setPdnDisconnectionTimeoutMs(int timeout) {
+        mPdnDisconnectionTimeoutMs = timeout;
+    }
+
+    private void exitEmergencyModeIfDelayed() {
+        synchronized (mLock) {
+            if (mPhoneToExit != null) {
+                unregisterForDataConnectionStateChanges();
+                mPhoneToExit.exitEmergencyMode(
+                        mHandler.obtainMessage(MSG_EXIT_EMERGENCY_MODE_DONE,
+                                Integer.valueOf(EMERGENCY_TYPE_CALL)));
+                mPhoneToExit = null;
+            }
+        }
+    }
+
+    /**
+     * Registers for changes to data connection state.
+     */
+    private void registerForDataConnectionStateChanges(Phone phone) {
+        if ((mDataConnectionStateListener != null) || (phone == null)) {
+            return;
+        }
+        Rlog.i(TAG, "registerForDataConnectionStateChanges");
+
+        mDataConnectionStateListener = new PreciseDataConnectionStateListener();
+        mTelephonyManagerProxy.registerTelephonyCallback(phone.getSubId(),
+                mHandler::post, mDataConnectionStateListener);
+    }
+
+    /**
+     * Unregisters for changes to data connection state.
+     */
+    private void unregisterForDataConnectionStateChanges() {
+        if (mDataConnectionStateListener == null) {
+            return;
+        }
+        Rlog.i(TAG, "unregisterForDataConnectionStateChanges");
+
+        mTelephonyManagerProxy.unregisterTelephonyCallback(mDataConnectionStateListener);
+        mDataConnectionStateListener = null;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
index 9c4ebfa..384112d 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
@@ -96,7 +96,7 @@
                 continue;
             }
 
-            int timeoutCallbackInterval = (forEmergencyCall && phone == phoneForEmergencyCall)
+            int timeoutCallbackInterval = (phone == phoneForEmergencyCall)
                     ? emergencyTimeoutIntervalMillis : 0;
             mInProgressListeners.add(mListeners.get(i));
             mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall, forEmergencyCall
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
index d61c146..5949f66 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
@@ -22,7 +22,7 @@
 import android.os.Message;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
-import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.ISatelliteModemStateCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
@@ -142,7 +142,8 @@
         }
     };
 
-    private final ISatelliteStateCallback mSatelliteCallback = new ISatelliteStateCallback.Stub() {
+    private final ISatelliteModemStateCallback mSatelliteCallback =
+            new ISatelliteModemStateCallback.Stub() {
         @Override
         public void onSatelliteModemStateChanged(int state) {
             mHandler.obtainMessage(MSG_SATELLITE_ENABLED_CHANGED).sendToTarget();
@@ -504,7 +505,7 @@
         if (mPhone != null) {
             subId = mPhone.getSubId();
         }
-        mSatelliteController.unregisterForSatelliteModemStateChanged(subId, mSatelliteCallback);
+        mSatelliteController.unregisterForModemStateChanged(subId, mSatelliteCallback);
         mHandler.removeMessages(MSG_SATELLITE_ENABLED_CHANGED);
     }
 
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
index 2f73c91..6e1c8dd 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
@@ -16,14 +16,20 @@
 
 package com.android.internal.telephony.euicc;
 
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_EUICC;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -39,6 +45,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccController;
@@ -66,6 +73,8 @@
     private SimSlotStatusChangedBroadcastReceiver mSimSlotStatusChangeReceiver;
     private EuiccController mEuiccController;
     private UiccController mUiccController;
+    private FeatureFlags mFeatureFlags;
+    private PackageManager mPackageManager;
 
     private static EuiccCardController sInstance;
 
@@ -87,10 +96,10 @@
     }
 
     /** Initialize the instance. Should only be called once. */
-    public static EuiccCardController init(Context context) {
+    public static EuiccCardController init(Context context, FeatureFlags featureFlags) {
         synchronized (EuiccCardController.class) {
             if (sInstance == null) {
-                sInstance = new EuiccCardController(context);
+                sInstance = new EuiccCardController(context, featureFlags);
             } else {
                 Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
             }
@@ -110,8 +119,9 @@
         return sInstance;
     }
 
-    private EuiccCardController(Context context) {
-        this(context, new Handler(), EuiccController.get(), UiccController.getInstance());
+    private EuiccCardController(Context context, FeatureFlags featureFlags) {
+        this(context, new Handler(), EuiccController.get(), UiccController.getInstance(),
+                featureFlags);
         TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
                 .getEuiccCardControllerServiceRegisterer()
@@ -123,13 +133,16 @@
             Context context,
             Handler handler,
             EuiccController euiccController,
-            UiccController uiccController) {
+            UiccController uiccController,
+            FeatureFlags featureFlags) {
         mContext = context;
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
 
         mEuiccMainThreadHandler = handler;
         mUiccController = uiccController;
         mEuiccController = euiccController;
+        mFeatureFlags = featureFlags;
+        mPackageManager = context.getPackageManager();
 
         if (isBootUp(mContext)) {
             mSimSlotStatusChangeReceiver = new SimSlotStatusChangedBroadcastReceiver();
@@ -293,6 +306,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getAllProfiles");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -342,6 +357,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getProfile");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -390,6 +407,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getEnabledProfile");
+
         String iccId = null;
         boolean isValidSlotPort = false;
         // get the iccid whether or not the port is active
@@ -474,6 +493,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "disableProfile");
+
         EuiccPort port = getEuiccPortFromIccId(cardId, iccid);
         if (port == null) {
             try {
@@ -522,6 +543,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "switchToProfile");
+
         EuiccPort port = getEuiccPort(cardId, portIndex);
         if (port == null) {
             try {
@@ -588,6 +611,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "setNickname");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -636,6 +661,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "deleteProfile");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -687,6 +714,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "resetMemory");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -738,6 +767,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getDefaultSmdpAddress");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -786,6 +817,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getSmdsAddress");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -834,6 +867,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "setDefaultSmdpAddress");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -882,6 +917,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getRulesAuthTable");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -931,6 +968,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getEuiccChallenge");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -979,6 +1018,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getEuiccInfo1");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1027,6 +1068,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getEuiccInfo2");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1076,6 +1119,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "authenticateServer");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1126,6 +1171,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "prepareDownload");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1175,6 +1222,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "loadBoundProfilePackage");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1226,6 +1275,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "cancelSession");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1274,6 +1325,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "listNotifications");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1323,6 +1376,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "retrieveNotificationList");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1372,6 +1427,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "retrieveNotification");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1421,6 +1478,8 @@
             return;
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "removeNotificationFromList");
+
         EuiccPort port = getFirstActiveEuiccPort(cardId);
         if (port == null) {
             try {
@@ -1469,6 +1528,29 @@
         Binder.restoreCallingIdentity(token);
     }
 
+    /**
+     * Make sure the device has required telephony feature
+     *
+     * @throws UnsupportedOperationException if the device does not have required telephony feature
+     */
+    private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+            @NonNull String methodName) {
+        if (callingPackage == null || mPackageManager == null) {
+            return;
+        }
+
+        if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+                || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+                Binder.getCallingUserHandle())) {
+            return;
+        }
+
+        if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY_EUICC)) {
+            throw new UnsupportedOperationException(
+                    methodName + " is unsupported without " + FEATURE_TELEPHONY_EUICC);
+        }
+    }
+
     private static void loge(String message) {
         Log.e(TAG, message);
     }
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index c417a34..3c444ef 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
@@ -47,6 +47,7 @@
 import android.service.euicc.IEraseSubscriptionsCallback;
 import android.service.euicc.IEuiccService;
 import android.service.euicc.IEuiccServiceDumpResultCallback;
+import android.service.euicc.IGetAvailableMemoryInBytesCallback;
 import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
 import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
 import android.service.euicc.IGetEidCallback;
@@ -155,6 +156,7 @@
     private static final int CMD_START_OTA_IF_NECESSARY = 112;
     private static final int CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS = 113;
     private static final int CMD_DUMP_EUICC_SERVICE = 114;
+    private static final int CMD_GET_AVAILABLE_MEMORY_IN_BYTES = 115;
 
     private static boolean isEuiccCommand(int what) {
         return what >= CMD_GET_EID;
@@ -208,6 +210,13 @@
         void onGetEidComplete(String eid);
     }
 
+    /** Callback class for {@link #getAvailableMemoryInBytes}. */
+    @VisibleForTesting(visibility = PACKAGE)
+    public interface GetAvailableMemoryInBytesCommandCallback extends BaseEuiccCommandCallback {
+        /** Called when the available memory in bytes lookup has completed. */
+        void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes);
+    }
+
     /** Callback class for {@link #getOtaStatus}. */
     @VisibleForTesting(visibility = PACKAGE)
     public interface GetOtaStatusCommandCallback extends BaseEuiccCommandCallback {
@@ -436,6 +445,13 @@
         sendMessage(CMD_GET_EID, cardId, 0 /* arg2 */, callback);
     }
 
+    /** Asynchronously fetch the available memory in bytes. */
+    @VisibleForTesting(visibility = PACKAGE)
+    public void getAvailableMemoryInBytes(
+            int cardId, GetAvailableMemoryInBytesCommandCallback callback) {
+        sendMessage(CMD_GET_AVAILABLE_MEMORY_IN_BYTES, cardId, 0 /* arg2 */, callback);
+    }
+
     /** Asynchronously get OTA status. */
     @VisibleForTesting(visibility = PACKAGE)
     public void getOtaStatus(int cardId, GetOtaStatusCommandCallback callback) {
@@ -760,6 +776,22 @@
                                     });
                             break;
                         }
+                        case CMD_GET_AVAILABLE_MEMORY_IN_BYTES: {
+                            mEuiccService.getAvailableMemoryInBytes(slotId,
+                                    new IGetAvailableMemoryInBytesCallback.Stub() {
+                                        @Override
+                                        public void onSuccess(long availableMemoryInBytes) {
+                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
+                                                ((GetAvailableMemoryInBytesCommandCallback)
+                                                        callback)
+                                                        .onGetAvailableMemoryInBytesComplete(
+                                                                availableMemoryInBytes);
+                                                onCommandEnd(callback);
+                                            });
+                                        }
+                                    });
+                            break;
+                        }
                         case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: {
                             GetMetadataRequest request = (GetMetadataRequest) message.obj;
                             mEuiccService.getDownloadableSubscriptionMetadata(slotId,
@@ -1036,6 +1068,7 @@
             case CMD_GET_OTA_STATUS:
             case CMD_START_OTA_IF_NECESSARY:
             case CMD_DUMP_EUICC_SERVICE:
+            case CMD_GET_AVAILABLE_MEMORY_IN_BYTES:
                 return (BaseEuiccCommandCallback) message.obj;
             case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA:
                 return ((GetMetadataRequest) message.obj).mCallback;
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index c8f3368..6dd6f65 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -15,12 +15,17 @@
  */
 package com.android.internal.telephony.euicc;
 
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_EUICC;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
+
 import android.Manifest;
 import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
 import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.Context;
@@ -30,6 +35,8 @@
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.euicc.DownloadSubscriptionResult;
 import android.service.euicc.EuiccService;
@@ -51,6 +58,7 @@
 import android.telephony.euicc.EuiccManager;
 import android.telephony.euicc.EuiccManager.OtaStatus;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
@@ -60,6 +68,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.UiccController;
@@ -68,13 +77,16 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.Stack;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 /** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */
 public class EuiccController extends IEuiccController.Stub {
@@ -111,17 +123,19 @@
     private final TelephonyManager mTelephonyManager;
     private final AppOpsManager mAppOpsManager;
     private final PackageManager mPackageManager;
+    private final FeatureFlags mFeatureFlags;
 
     // 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.
     private List<String> mSupportedCountries;
     private List<String> mUnsupportedCountries;
+    private List<Integer> mPsimConversionSupportedCarrierIds;
 
     /** Initialize the instance. Should only be called once. */
-    public static EuiccController init(Context context) {
+    public static EuiccController init(Context context, FeatureFlags featureFlags) {
         synchronized (EuiccController.class) {
             if (sInstance == null) {
-                sInstance = new EuiccController(context);
+                sInstance = new EuiccController(context, featureFlags);
             } else {
                 Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
             }
@@ -141,14 +155,14 @@
         return sInstance;
     }
 
-    private EuiccController(Context context) {
-        this(context, new EuiccConnector(context));
+    private EuiccController(Context context, FeatureFlags featureFlags) {
+        this(context, new EuiccConnector(context), featureFlags);
         TelephonyFrameworkInitializer
                 .getTelephonyServiceManager().getEuiccControllerService().register(this);
     }
 
     @VisibleForTesting
-    public EuiccController(Context context, EuiccConnector connector) {
+    public EuiccController(Context context, EuiccConnector connector, FeatureFlags featureFlags) {
         mContext = context;
         mConnector = connector;
         mSubscriptionManager = (SubscriptionManager)
@@ -157,6 +171,7 @@
                 context.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mPackageManager = context.getPackageManager();
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -245,6 +260,36 @@
     }
 
     /**
+     * Return the available memory in bytes of the eUICC.
+     *
+     * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
+     * that IPC should generally be fast, and the available memory shouldn't be needed in the normal
+     * course of operation.
+     */
+    @Override
+    public long getAvailableMemoryInBytes(int cardId, String callingPackage) {
+        boolean callerCanReadPhoneStatePrivileged = callerCanReadPhoneStatePrivileged();
+        boolean callerCanReadPhoneState = callerCanReadPhoneState();
+        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
+        long token = Binder.clearCallingIdentity();
+        try {
+            if (!callerCanReadPhoneStatePrivileged
+                    && !callerCanReadPhoneState
+                    && !canManageSubscriptionOnTargetSim(
+                            cardId, callingPackage, false, TelephonyManager.INVALID_PORT_INDEX)) {
+                throw new SecurityException(
+                        "Must have READ_PHONE_STATE permission or READ_PRIVILEGED_PHONE_STATE"
+                            + " permission or carrier privileges to read the available memory for"
+                            + "cardId="
+                                + cardId);
+            }
+            return blockingGetAvailableMemoryInBytesFromEuiccService(cardId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
      * Return the current status of OTA update.
      *
      * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
@@ -571,6 +616,19 @@
             boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim,
             Bundle resolvedBundle, PendingIntent callbackIntent) {
         boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
+        boolean callerCanDownloadAdminManagedSubscription =
+                Flags.esimManagementEnabled()
+                        && callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
+        if (Flags.esimManagementEnabled()) {
+            if (mContext
+                    .getSystemService(UserManager.class)
+                    .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY)
+                    && !callerCanDownloadAdminManagedSubscription) {
+                // 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");
+            }
+        }
         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.
@@ -588,18 +646,27 @@
                 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
                     + " isConsentNeededToResolvePortIndex: " + isConsentNeededToResolvePortIndex
-                    + " shouldResolvePortIndex:" + shouldResolvePortIndex);
-            if (!isConsentNeededToResolvePortIndex && callerCanWriteEmbeddedSubscriptions) {
+                    + " shouldResolvePortIndex:" + shouldResolvePortIndex
+                    + " hasAdminPrivileges:" + hasAdminPrivileges);
+            if (!isConsentNeededToResolvePortIndex
+                    && (callerCanWriteEmbeddedSubscriptions
+                                    || hasAdminPrivileges)) {
                 // 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);
+                        callbackIntent, callerCanDownloadAdminManagedSubscription,
+                        getCurrentEmbeddedSubscriptionIds(cardId));
                 return;
             }
 
@@ -735,11 +802,30 @@
                         true /* withUserConsent */, portIndex));
     }
 
+    void downloadSubscriptionPrivileged(int cardId, int portIndex, final long callingToken,
+            DownloadableSubscription subscription, boolean switchAfterDownload,
+            boolean forceDeactivateSim, final String callingPackage,
+            Bundle resolvedBundle, final PendingIntent callbackIntent) {
+        downloadSubscriptionPrivileged(
+                cardId,
+                portIndex,
+                callingToken,
+                subscription,
+                switchAfterDownload,
+                forceDeactivateSim,
+                callingPackage,
+                resolvedBundle,
+                callbackIntent,
+                false /* markAsOwnedByAdmin */,
+                new ArraySet<>() /* existingSubscriptions */);
+    }
+
     // Continue to download subscription without checking anything.
     void downloadSubscriptionPrivileged(int cardId, int portIndex, final long callingToken,
             DownloadableSubscription subscription, boolean switchAfterDownload,
             boolean forceDeactivateSim, final String callingPackage, Bundle resolvedBundle,
-            final PendingIntent callbackIntent) {
+            final PendingIntent callbackIntent, boolean markAsOwnedByAdmin,
+            Set<Integer> existingSubscriptions) {
         mConnector.downloadSubscription(
                 cardId,
                 portIndex,
@@ -768,7 +854,13 @@
                                     // Since we're not switching, nothing will trigger a
                                     // subscription list refresh on its own, so request one here.
                                     refreshSubscriptionsAndSendResult(
-                                            callbackIntent, resultCode, extrasIntent);
+                                            callbackIntent,
+                                            resultCode,
+                                            extrasIntent,
+                                            markAsOwnedByAdmin,
+                                            callingPackage,
+                                            cardId,
+                                            existingSubscriptions);
                                     return;
                                 }
                                 break;
@@ -970,6 +1062,9 @@
     public void deleteSubscription(int cardId, int subscriptionId, String callingPackage,
             PendingIntent callbackIntent) {
         boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
+        boolean callerIsAdmin =
+                Flags.esimManagementEnabled()
+                        && callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
 
         long token = Binder.clearCallingIdentity();
@@ -980,13 +1075,14 @@
                 sendResult(callbackIntent, ERROR, null /* extrasIntent */);
                 return;
             }
-
+            boolean adminOwned = callerIsAdmin && sub.getGroupOwner().equals(callingPackage);
             // For both single active SIM device and multi-active SIM device, if the caller is
             // system or the caller manage the target subscription, we let it continue. This is
             // because deleting subscription won't change status of any other subscriptions.
             if (!callerCanWriteEmbeddedSubscriptions
-                    && !mSubscriptionManager.canManageSubscription(sub, callingPackage)) {
-                Log.e(TAG, "No permissions: " + subscriptionId);
+                    && !mSubscriptionManager.canManageSubscription(sub, callingPackage)
+                    && !adminOwned) {
+                Log.e(TAG, "No permissions: " + subscriptionId + " adminOwned=" + adminOwned);
                 sendResult(callbackIntent, ERROR, null /* extrasIntent */);
                 return;
             }
@@ -1604,9 +1700,43 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public void refreshSubscriptionsAndSendResult(
             PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
+        refreshSubscriptionsAndSendResult(
+                callbackIntent,
+                resultCode,
+                extrasIntent,
+                /* isCallerAdmin= */ false,
+                /* callingPackage= */ "",
+                /* cardId */ -1,
+                /* subscriptionsBefore= */ new ArraySet<>());
+    }
+
+    /** Refresh the embedded subscription list and dispatch the given result upon completion. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void refreshSubscriptionsAndSendResult(
+            PendingIntent callbackIntent,
+            int resultCode,
+            Intent extrasIntent,
+            boolean isCallerAdmin,
+            String callingPackage,
+            int cardId,
+            Set<Integer> subscriptionsBefore) {
         SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
                 List.of(mTelephonyManager.getCardIdForDefaultEuicc()),
-                () -> sendResult(callbackIntent, resultCode, extrasIntent));
+                () -> {
+                    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);
+                        }
+                    }
+                    sendResult(callbackIntent, resultCode, extrasIntent);
+                });
+
     }
 
     /** Dispatch the given callback intent with the given result code and data. */
@@ -1722,6 +1852,23 @@
         return null;
     }
 
+    private Set<Integer> getCurrentEmbeddedSubscriptionIds(int cardId) {
+        if (!Flags.esimManagementEnabled()) {
+            return new ArraySet<>();
+        }
+        List<SubscriptionInfo> subscriptionInfos =
+                mSubscriptionManager.getAvailableSubscriptionInfoList();
+        int subCount = (subscriptionInfos != null) ? subscriptionInfos.size() : 0;
+        Set<Integer> currentEmbeddedSubscriptionIds = new ArraySet<>();
+        for (int i = 0; i < subCount; i++) {
+            SubscriptionInfo subscriptionInfo = subscriptionInfos.get(i);
+            if (subscriptionInfo.isEmbedded() && subscriptionInfo.getCardId() == cardId) {
+                currentEmbeddedSubscriptionIds.add(subscriptionInfo.getSubscriptionId());
+            }
+        }
+        return currentEmbeddedSubscriptionIds;
+    }
+
     @Nullable
     private String blockingGetEidFromEuiccService(int cardId) {
         CountDownLatch latch = new CountDownLatch(1);
@@ -1741,6 +1888,27 @@
         return awaitResult(latch, eidRef);
     }
 
+    private long blockingGetAvailableMemoryInBytesFromEuiccService(int cardId) {
+        CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<Long> memoryRef =
+                new AtomicReference<>(EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE);
+        mConnector.getAvailableMemoryInBytes(
+                cardId,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        memoryRef.set(availableMemoryInBytes);
+                        latch.countDown();
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        latch.countDown();
+                    }
+                });
+        return awaitResult(latch, memoryRef);
+    }
+
     private @OtaStatus int blockingGetOtaStatusFromEuiccService(int cardId) {
         CountDownLatch latch = new CountDownLatch(1);
         AtomicReference<Integer> statusRef =
@@ -1948,15 +2116,55 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private boolean callerCanReadPhoneState() {
+        return mContext.checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     private boolean callerCanWriteEmbeddedSubscriptions() {
         return mContext.checkCallingOrSelfPermission(
                 Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(UserHandle userHandle) {
+        Context userContext;
+        long ident = Binder.clearCallingIdentity();
+        try {
+            userContext = mContext.createPackageContextAsUser(
+                    mContext.getPackageName(), /* flags= */ 0, userHandle);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Unknown package name");
+            return null;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return userContext.getSystemService(DevicePolicyManager.class);
+    }
+
+    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 =
+                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;
+    }
+
     @Override
     public boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage) {
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
+
+        enforceTelephonyFeatureWithException(callingPackage, "isSimPortAvailable");
+
         // If calling app is targeted for Android U and beyond, check for other conditions
         // to decide the port availability.
         boolean shouldCheckConditionsForInactivePort = isCompatChangeEnabled(callingPackage,
@@ -2063,4 +2271,55 @@
                 + " changeEnabled: " + changeEnabled);
         return changeEnabled;
     }
+
+
+    @Override
+    public void setPsimConversionSupportedCarriers(int[] carrierIds) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to "
+                            + "set pSIM conversion supported carriers");
+        }
+        mPsimConversionSupportedCarrierIds = Arrays.stream(carrierIds).boxed()
+                .collect(Collectors.toList());
+    }
+
+
+
+    @Override
+    public boolean isPsimConversionSupported(int carrierId) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS "
+                            + "to check if the carrier is supported pSIM conversion");
+        }
+        if (mPsimConversionSupportedCarrierIds == null
+                || mPsimConversionSupportedCarrierIds.isEmpty()) {
+            return false;
+        }
+        return mPsimConversionSupportedCarrierIds.contains(carrierId);
+    }
+
+    /**
+     * Make sure the device has required telephony feature
+     *
+     * @throws UnsupportedOperationException if the device does not have required telephony feature
+     */
+    private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+            @NonNull String methodName) {
+        if (callingPackage == null || mPackageManager == null) {
+            return;
+        }
+
+        if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+                || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+                Binder.getCallingUserHandle())) {
+            return;
+        }
+
+        if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY_EUICC)) {
+            throw new UnsupportedOperationException(
+                    methodName + " is unsupported without " + FEATURE_TELEPHONY_EUICC);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 5b1f36d..dae808a 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -171,6 +171,7 @@
             if(mPhone.getServiceState().getRilDataRadioTechnology()
                     != ServiceState.RIL_RADIO_TECHNOLOGY_NR) {
                 tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
+                notifySmsSentFailedToEmergencyStateTracker(tracker);
                 return;
             }
         }
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
index 13ec750..778bd0e 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
@@ -118,13 +118,13 @@
     public final void disableIms(int slotId, int subId) {
         MmTelFeatureCompatAdapter adapter = mMmTelCompatAdapters.get(slotId);
         if (adapter == null) {
-            Log.w(TAG, "enableIms: adapter null for slot :" + slotId);
+            Log.w(TAG, "disableIms: adapter null for slot :" + slotId);
             return;
         }
         try {
             adapter.disableIms();
         } catch (RemoteException e) {
-            Log.w(TAG, "Couldn't enable IMS: " + e.getMessage());
+            Log.w(TAG, "Couldn't disableIms IMS: " + e.getMessage());
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
index 3dedde8..234723f 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
@@ -263,9 +263,10 @@
             PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(),
                     KEY_NR_SA_DISABLE_POLICY_INT, KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
             mNrSaDisablePolicy = bundle.getInt(KEY_NR_SA_DISABLE_POLICY_INT);
-            mIsNrSaSupported = Arrays.stream(
-                    bundle.getIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY)).anyMatch(
-                        value -> value == CARRIER_NR_AVAILABILITY_SA);
+            int[] nrAvailabilities = bundle.getIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
+            mIsNrSaSupported = nrAvailabilities != null
+                    && Arrays.stream(nrAvailabilities).anyMatch(
+                            value -> value == CARRIER_NR_AVAILABILITY_SA);
 
             Log.d(TAG, "setNrSaDisablePolicy : NrSaDisablePolicy = "
                     + mNrSaDisablePolicy + ", IsNrSaSupported = "  + mIsNrSaSupported);
@@ -286,7 +287,7 @@
 
     private void setNrSaMode(boolean onOrOff) {
         if (mIsNrSaSupported) {
-            mPhone.getDefaultPhone().mCi.setN1ModeEnabled(onOrOff, null);
+            mPhone.getDefaultPhone().setN1ModeEnabled(onOrOff, null);
             Log.i(TAG, "setNrSaMode : " + onOrOff);
 
             setNrSaDisabledForWfc(!onOrOff);
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 54a378e..f4f632e 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -21,8 +21,10 @@
 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.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;
 import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT;
+import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_RAT_BLOCK;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
 
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC;
@@ -120,6 +122,7 @@
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.metrics.ImsStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
@@ -454,14 +457,15 @@
     }
 
     // Constructors
-    public ImsPhone(Context context, PhoneNotifier notifier, Phone defaultPhone) {
-        this(context, notifier, defaultPhone, ImsManager::getInstance, false);
+    public ImsPhone(Context context, PhoneNotifier notifier,
+            Phone defaultPhone, FeatureFlags featureFlags) {
+        this(context, notifier, defaultPhone, ImsManager::getInstance, false, featureFlags);
     }
 
     @VisibleForTesting
     public ImsPhone(Context context, PhoneNotifier notifier, Phone defaultPhone,
-            ImsManagerFactory imsManagerFactory, boolean unitTestMode) {
-        super("ImsPhone", context, notifier, unitTestMode);
+            ImsManagerFactory imsManagerFactory, boolean unitTestMode, FeatureFlags featureFlags) {
+        super("ImsPhone", context, notifier, unitTestMode, featureFlags);
 
         mDefaultPhone = defaultPhone;
         mImsManagerFactory = imsManagerFactory;
@@ -480,7 +484,7 @@
                         .inject(ImsNrSaModeHandler.class.getName())
                         .makeImsNrSaModeHandler(this);
         mCT = TelephonyComponentFactory.getInstance().inject(ImsPhoneCallTracker.class.getName())
-                .makeImsPhoneCallTracker(this);
+                .makeImsPhoneCallTracker(this, featureFlags);
         mCT.registerPhoneStateListener(mExternalCallTracker);
         mExternalCallTracker.setCallPuller(mCT);
 
@@ -2430,7 +2434,9 @@
 
         if (mCT.getState() == PhoneConstants.State.IDLE) {
             if (DBG) logd("updateRoamingState now: " + newRoamingState);
-            mLastKnownRoamingState = newRoamingState;
+            if (!mFeatureFlags.updateRoamingStateToSetWfcMode()) {
+                mLastKnownRoamingState = newRoamingState;
+            }
             CarrierConfigManager configManager = (CarrierConfigManager)
                     getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
             // Don't set wfc mode if carrierconfig has not loaded. It will be set by GsmCdmaPhone
@@ -2439,6 +2445,9 @@
                     configManager.getConfigForSubId(getSubId()))) {
                 ImsManager imsManager = mImsManagerFactory.create(mContext, mPhoneId);
                 imsManager.setWfcMode(imsManager.getWfcMode(newRoamingState), newRoamingState);
+                if (mFeatureFlags.updateRoamingStateToSetWfcMode()) {
+                    mLastKnownRoamingState = newRoamingState;
+                }
             }
         } else {
             if (DBG) logd("updateRoamingState postponed: " + newRoamingState);
@@ -2523,10 +2532,21 @@
                 if ((suggestedAction == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK)
                         || (suggestedAction == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT)) {
                     suggestedModemAction = suggestedAction;
+                } else if (mFeatureFlags.addRatRelatedSuggestedActionToImsRegistration()) {
+                    if ((suggestedAction == SUGGESTED_ACTION_TRIGGER_RAT_BLOCK)
+                            || (suggestedAction == SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS)) {
+                        suggestedModemAction = suggestedAction;
+                    }
                 }
             }
             updateImsRegistrationInfo(REGISTRATION_STATE_NOT_REGISTERED,
                     imsRadioTech, suggestedModemAction);
+
+            if (mFeatureFlags.clearCachedImsPhoneNumberWhenDeviceLostImsRegistration()) {
+                // Clear the phone number from P-Associated-Uri
+                setCurrentSubscriberUris(null);
+                clearPhoneNumberForSourceIms();
+            }
         }
 
         @Override
@@ -2537,6 +2557,18 @@
         }
     };
 
+    /** Clear the IMS phone number from IMS associated Uris when IMS registration is lost. */
+    @VisibleForTesting
+    public void clearPhoneNumberForSourceIms() {
+        int subId = getSubId();
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            return;
+        }
+
+        if (DBG) logd("clearPhoneNumberForSourceIms");
+        mSubscriptionManagerService.setNumberFromIms(subId, new String(""));
+    }
+
     /** Sets the IMS phone number from IMS associated URIs, if any found. */
     @VisibleForTesting
     public void setPhoneNumberForSourceIms(Uri[] uris) {
@@ -2706,9 +2738,13 @@
             @RegistrationManager.SuggestedAction int suggestedAction) {
 
         if (regState == mImsRegistrationState) {
+            // In NOT_REGISTERED state, the current PLMN can be blocked with a suggested action.
+            // But in this case, the same behavior is able to occur in different PLMNs with
+            // same radio tech and suggested action.
             if ((regState == REGISTRATION_STATE_REGISTERED && imsRadioTech == mImsRegistrationTech)
                     || (regState == REGISTRATION_STATE_NOT_REGISTERED
-                            && suggestedAction == mImsRegistrationSuggestedAction
+                            && suggestedAction == SUGGESTED_ACTION_NONE
+                            && mImsRegistrationSuggestedAction == SUGGESTED_ACTION_NONE
                             && imsRadioTech == mImsDeregistrationTech)) {
                 // Filter duplicate notification.
                 return;
@@ -2793,6 +2829,15 @@
         mCT.triggerNotifyAnbr(mediaType, direction, bitsPerSecond);
     }
 
+    /**
+     * Check whether making a call using Wi-Fi is possible or not.
+     * @return {code true} if IMS is registered over IWLAN else return {code false}.
+     */
+    public boolean canMakeWifiCall() {
+        return isImsRegistered() && (getImsRegistrationTech()
+                == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
index 8a1041d..c5a7ecf 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
@@ -43,6 +43,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.telephony.Rlog;
 
@@ -58,8 +59,9 @@
     private PhoneConstants.State mState = PhoneConstants.State.IDLE;
 
     public ImsPhoneBase(String name, Context context, PhoneNotifier notifier,
-                        boolean unitTestMode) {
-        super(name, notifier, context, new ImsPhoneCommandInterface(context), unitTestMode);
+                        boolean unitTestMode, FeatureFlags featureFlags) {
+        super(name, notifier, context, new ImsPhoneCommandInterface(context), unitTestMode,
+                featureFlags);
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index c3ee0f6..b5a052d 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -148,6 +148,7 @@
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsPhone.ImsDialArgs;
 import com.android.internal.telephony.metrics.CallQualityMetrics;
@@ -310,8 +311,11 @@
                         // activeCall could be null if the foreground call is in a disconnected
                         // state.  If either of the calls is null there is no need to check if
                         // one will be disconnected on answer.
+                        // Use VideoProfile.STATE_BIDIRECTIONAL to not affect existing
+                        // implementation. Video state of user response is handled in acceptCall().
                         boolean answeringWillDisconnect =
-                                shouldDisconnectActiveCallOnAnswer(activeCall, imsCall);
+                                shouldDisconnectActiveCallOnAnswer(activeCall, imsCall,
+                                        VideoProfile.STATE_BIDIRECTIONAL);
                         conn.setActiveCallDisconnectedOnAnswer(answeringWillDisconnect);
                     }
                 }
@@ -664,6 +668,7 @@
     private static final int EVENT_START_IMS_TRAFFIC_DONE = 33;
     private static final int EVENT_CONNECTION_SETUP_FAILURE = 34;
     private static final int EVENT_NEW_ACTIVE_CALL_STARTED = 35;
+    private static final int EVENT_PROVISIONING_CHANGED = 36;
 
     private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
 
@@ -673,6 +678,8 @@
 
     private static final int TIMEOUT_PARTICIPANT_CONNECT_TIME_CACHE_MS = 60000; //ms
 
+    private static final int DELAY_STACKING_PROVISIONING_CHANGES_MILLIS = 50; //ms
+
     // Following values are for mHoldSwitchingState
     private enum HoldSwapState {
         // Not in the middle of a hold/swap operation
@@ -1235,16 +1242,37 @@
         }
     }
 
+    private final ConcurrentLinkedQueue<ProvisioningItem> mProvisioningItemQueue =
+            new ConcurrentLinkedQueue<>();
+
+    private static class ProvisioningItem {
+        final int mItem;
+        final Object mValue;
+        ProvisioningItem(int item, int value) {
+            this.mItem = item;
+            this.mValue = Integer.valueOf(value);
+        }
+
+        ProvisioningItem(int item, String value) {
+            this.mItem = item;
+            this.mValue = value;
+        }
+    }
+
     //***** Events
 
 
     //***** Constructors
-    public ImsPhoneCallTracker(ImsPhone phone, ConnectorFactory factory) {
-        this(phone, factory, phone.getContext().getMainExecutor());
+    public ImsPhoneCallTracker(ImsPhone phone, ConnectorFactory factory,
+            FeatureFlags featureFlags) {
+        this(phone, factory, phone.getContext().getMainExecutor(), featureFlags);
     }
 
     @VisibleForTesting
-    public ImsPhoneCallTracker(ImsPhone phone, ConnectorFactory factory, Executor executor) {
+    public ImsPhoneCallTracker(ImsPhone phone, ConnectorFactory factory, Executor executor,
+            FeatureFlags featureFlags) {
+        super(featureFlags);
+
         this.mPhone = phone;
         mConnectorFactory = factory;
         if (executor != null) {
@@ -2189,7 +2217,7 @@
             ImsCall ringingCall = mRingingCall.getImsCall();
             if (mForegroundCall.hasConnections() && mRingingCall.hasConnections()) {
                 answeringWillDisconnect =
-                        shouldDisconnectActiveCallOnAnswer(activeCall, ringingCall);
+                        shouldDisconnectActiveCallOnAnswer(activeCall, ringingCall, videoState);
             }
 
             // Cache video state for pending MT call.
@@ -4424,13 +4452,15 @@
         public void onCallQualityChanged(ImsCall imsCall, CallQuality callQuality) {
             // convert ServiceState.radioTech to TelephonyManager.NetworkType constant
             mPhone.onCallQualityChanged(callQuality, imsCall.getNetworkType());
-            String callId = imsCall.getSession().getCallId();
-            CallQualityMetrics cqm = mCallQualityMetrics.get(callId);
-            if (cqm == null) {
-                cqm = new CallQualityMetrics(mPhone);
+            if (imsCall.getSession() != null) {
+                String callId = imsCall.getSession().getCallId();
+                CallQualityMetrics cqm = mCallQualityMetrics.get(callId);
+                if (cqm == null) {
+                    cqm = new CallQualityMetrics(mPhone);
+                }
+                cqm.saveCallQuality(callQuality);
+                mCallQualityMetrics.put(callId, cqm);
             }
-            cqm.saveCallQuality(callQuality);
-            mCallQualityMetrics.put(callId, cqm);
 
             ImsPhoneConnection conn = findConnection(imsCall);
             if (conn != null) {
@@ -4590,37 +4620,67 @@
 
     private final ProvisioningManager.Callback mConfigCallback =
             new ProvisioningManager.Callback() {
-        @Override
-        public void onProvisioningIntChanged(int item, int value) {
-            sendConfigChangedIntent(item, Integer.toString(value));
-            if ((mImsManager != null)
-                    && (item == ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED
-                    || item == ImsConfig.ConfigConstants.VLT_SETTING_ENABLED
-                    || item == ImsConfig.ConfigConstants.LVC_SETTING_ENABLED)) {
-                // Update Ims Service state to make sure updated provisioning values take effect
-                // immediately.
-                updateImsServiceConfig();
-            }
-        }
+                @Override
+                public void onProvisioningIntChanged(int item, int value) {
+                    // if updateImsServiceByGatheringProvisioningChanges feature is enabled,
+                    // Provisioning items are processed all at once by queuing and sending message.
+                    if (mFeatureFlags.updateImsServiceByGatheringProvisioningChanges()) {
+                        queueAndSendProvisioningChanged(new ProvisioningItem(item, value));
+                        return;
+                    }
+                    // run belows when updateImsServiceByGatheringProvisioningChanges feature is
+                    // disabled only
 
-        @Override
-        public void onProvisioningStringChanged(int item, String value) {
-            sendConfigChangedIntent(item, value);
-        }
+                    sendConfigChangedIntent(item, Integer.toString(value));
+                    if ((mImsManager != null)
+                            && (item == ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED
+                            || item == ImsConfig.ConfigConstants.VLT_SETTING_ENABLED
+                            || item == ImsConfig.ConfigConstants.LVC_SETTING_ENABLED)) {
+                        // Update Ims Service state to make sure updated provisioning values take
+                        // effect immediately.
+                        updateImsServiceConfig();
+                    }
+                }
 
-        // send IMS_CONFIG_CHANGED intent for older services that do not implement the new callback
-        // interface.
-        private void sendConfigChangedIntent(int item, String value) {
-            log("sendConfigChangedIntent - [" + item + ", " + value + "]");
-            Intent configChangedIntent = new Intent(ImsConfig.ACTION_IMS_CONFIG_CHANGED);
-            configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item);
-            configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value);
-            if (mPhone != null && mPhone.getContext() != null) {
-                mPhone.getContext().sendBroadcast(
-                        configChangedIntent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
-            }
-        }
-    };
+                @Override
+                public void onProvisioningStringChanged(int item, String value) {
+                    if (mFeatureFlags.updateImsServiceByGatheringProvisioningChanges()) {
+                        queueAndSendProvisioningChanged(new ProvisioningItem(item, value));
+                        return;
+                    }
+                    // run belows when updateImsServiceByGatheringProvisioningChanges feature is
+                    // disabled only
+
+                    sendConfigChangedIntent(item, value);
+                }
+
+                // send IMS_CONFIG_CHANGED intent for older services that do not implement the new
+                // callback interface.
+                private void sendConfigChangedIntent(int item, String value) {
+                    log("sendConfigChangedIntent - [" + item + ", " + value + "]");
+                    Intent configChangedIntent = new Intent(ImsConfig.ACTION_IMS_CONFIG_CHANGED);
+                    configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item);
+                    configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value);
+                    if (mPhone != null && mPhone.getContext() != null) {
+                        mPhone.getContext().sendBroadcast(configChangedIntent,
+                                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+                    }
+                }
+
+                private void queueAndSendProvisioningChanged(ProvisioningItem provisioningItem) {
+                    if (!mFeatureFlags.updateImsServiceByGatheringProvisioningChanges()) {
+                        return;
+                    }
+
+                    boolean bQueueOffer = mProvisioningItemQueue.offer(provisioningItem);
+                    // Checks the Handler Message Queue and schedules a new message with small delay
+                    // to avoid stacking multiple redundant event only if it doesn't exist.
+                    if (bQueueOffer && !hasMessages(EVENT_PROVISIONING_CHANGED)) {
+                        sendMessageDelayed(obtainMessage(EVENT_PROVISIONING_CHANGED),
+                                DELAY_STACKING_PROVISIONING_CHANGES_MILLIS);
+                    }
+                }
+            };
 
     public void sendCallStartFailedDisconnect(ImsCall imsCall, ImsReasonInfo reasonInfo) {
         mPendingMO = null;
@@ -5011,6 +5071,11 @@
                 }
                 break;
             }
+
+            case EVENT_PROVISIONING_CHANGED: {
+                handleProvisioningChanged();
+                break;
+            }
         }
     }
 
@@ -5438,11 +5503,13 @@
      *
      * @param activeCall The active call.
      * @param incomingCall The incoming call.
+     * @param incomingCallVideoState The media type of incoming call acceptance.
+     *                              {@link VideoProfile.VideoState}
      * @return {@code true} if answering the incoming call will cause the active call to be
      *      disconnected, {@code false} otherwise.
      */
     private boolean shouldDisconnectActiveCallOnAnswer(ImsCall activeCall,
-            ImsCall incomingCall) {
+            ImsCall incomingCall, int incomingCallVideoState) {
 
         if (activeCall == null || incomingCall == null) {
             return false;
@@ -5457,7 +5524,14 @@
         boolean isActiveCallOnWifi = activeCall.isWifiCall();
         boolean isVoWifiEnabled = mImsManager.isWfcEnabledByPlatform()
                 && mImsManager.isWfcEnabledByUser();
-        boolean isIncomingCallAudio = !incomingCall.isVideoCall();
+        boolean isIncomingCallAudio = true;
+        if (!mFeatureFlags.terminateActiveVideoCallWhenAcceptingSecondVideoCallAsAudioOnly()) {
+            isIncomingCallAudio = !incomingCall.isVideoCall();
+        } else {
+            isIncomingCallAudio = !incomingCall.isVideoCall()
+                    || incomingCallVideoState == VideoProfile.STATE_AUDIO_ONLY;
+        }
+
         log("shouldDisconnectActiveCallOnAnswer : isActiveCallVideo=" + isActiveCallVideo +
                 " isActiveCallOnWifi=" + isActiveCallOnWifi + " isIncomingCallAudio=" +
                 isIncomingCallAudio + " isVowifiEnabled=" + isVoWifiEnabled);
@@ -6210,4 +6284,47 @@
         mImsTrafficSessions.forEachKey(1, token -> mPhone.stopImsTraffic(token, null));
         mImsTrafficSessions.clear();
     }
+
+    /**
+     * Process provisioning changes all at once.
+     */
+    private void handleProvisioningChanged() {
+        boolean bNeedUpdateImsServiceConfig = false;
+        ProvisioningItem provisioningItem;
+        while ((provisioningItem = mProvisioningItemQueue.poll()) != null) {
+            int item = provisioningItem.mItem;
+            if (provisioningItem.mValue instanceof Integer) {
+                sendConfigChangedIntent(item, provisioningItem.mValue.toString());
+                if (item == ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED
+                        || item == ImsConfig.ConfigConstants.VLT_SETTING_ENABLED
+                        || item == ImsConfig.ConfigConstants.LVC_SETTING_ENABLED) {
+                    bNeedUpdateImsServiceConfig = true;
+                }
+            } else if (provisioningItem.mValue instanceof String) {
+                sendConfigChangedIntent(item, provisioningItem.mValue.toString());
+            }
+        }
+        if (bNeedUpdateImsServiceConfig) {
+            // Update Ims Service state to make sure updated provisioning values take effect.
+            updateImsServiceConfig();
+        }
+    }
+
+    /**
+     * send IMS_CONFIG_CHANGED intent for older services that do not implement the new callback
+     * interface
+     *
+     * @param item provisioning item
+     * @param value provisioning value
+     */
+    private void sendConfigChangedIntent(int item, String value) {
+        log("sendConfigChangedIntent - [" + item + ", " + value + "]");
+        Intent configChangedIntent = new Intent(ImsConfig.ACTION_IMS_CONFIG_CHANGED);
+        configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item);
+        configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value);
+        if (mPhone != null && mPhone.getContext() != null) {
+            mPhone.getContext().sendBroadcast(
+                    configChangedIntent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index 7125763..a7a9129 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -57,14 +57,6 @@
     }
 
     @Override
-    public void getIccSlotsStatus(Message result) {
-    }
-
-    @Override
-    public void setLogicalToPhysicalSlotMapping(int[] physicalSlots, Message result) {
-    }
-
-    @Override
     public void supplyIccPin(String pin, Message result) {
     }
 
@@ -107,10 +99,6 @@
     }
 
     @Override
-    @Deprecated public void getPDPContextList(Message result) {
-    }
-
-    @Override
     public void getDataCallList(Message result) {
     }
 
@@ -134,14 +122,6 @@
     }
 
     @Override
-    public void getIMEI(Message result) {
-    }
-
-    @Override
-    public void getIMEISV(Message result) {
-    }
-
-    @Override
     public void hangupConnection (int gsmIndex, Message result) {
     }
 
@@ -189,15 +169,6 @@
     public void getLastCallFailCause (Message result) {
     }
 
-    @Deprecated
-    @Override
-    public void getLastPdpFailCause (Message result) {
-    }
-
-    @Override
-    public void getLastDataCallFailCause (Message result) {
-    }
-
     @Override
     public void setMute (boolean enableMute, Message response) {
     }
@@ -286,10 +257,9 @@
     }
 
     @Override
-    public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
-            boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
-            NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
-            boolean matchAllRuleAllowed, Message result) {
+    public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean allowRoaming,
+            int reason, LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
+            TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed, Message result) {
     }
 
     @Override
@@ -416,18 +386,6 @@
     }
 
     @Override
-    public void resetRadio(Message result) {
-    }
-
-    @Override
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-    }
-
-    @Override
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-    }
-
-    @Override
     public void setBandMode (int bandMode, Message response) {
     }
 
@@ -596,11 +554,11 @@
     }
 
     @Override
-    public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, Message result) {
+    public void setInitialAttachApn(DataProfile dataProfile, Message result) {
     }
 
     @Override
-    public void setDataProfile(DataProfile[] dps, boolean isRoaming, Message result) {
+    public void setDataProfile(DataProfile[] dps, Message result) {
     }
 
     @Override
@@ -640,18 +598,6 @@
     }
 
     @Override
-    public void startLceService(int reportIntervalMs, boolean pullMode, Message result) {
-    }
-
-    @Override
-    public void stopLceService(Message result) {
-    }
-
-    @Override
-    public void pullLceData(Message result) {
-    }
-
-    @Override
     public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
                                                 Message result) {
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index 104c925..a71355d 100755
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -1357,17 +1357,15 @@
      * @param imsCall The call to check for changes in extras.
      * @return Whether the extras fields have been changed.
      */
-     boolean updateExtras(ImsCall imsCall) {
+    boolean updateExtras(ImsCall imsCall) {
         if (imsCall == null) {
             return false;
         }
-
         final ImsCallProfile callProfile = imsCall.getCallProfile();
         final Bundle extras = callProfile != null ? callProfile.mCallExtras : null;
         if (extras == null && DBG) {
             Rlog.d(LOG_TAG, "Call profile extras are null.");
         }
-
         final boolean changed = !areBundlesEqual(extras, mExtras);
         if (changed) {
             updateImsCallRatFromExtras(extras);
@@ -1377,11 +1375,44 @@
             if (extras != null) {
                 mExtras.putAll(extras);
             }
+            if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
+                maybeInjectBusinessComposerExtras(mExtras);
+            }
             setConnectionExtras(mExtras);
         }
         return changed;
     }
 
+    /**
+     * The Ims Vendor is responsible for setting the ImsCallProfile business call composer
+     * values (ImsCallProfile.EXTRA_IS_BUSINESS_CALL and
+     * 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) {
+        if (extras == null) {
+            return;
+        }
+        try {
+            if (extras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL)
+                    && !extras.containsKey(android.telecom.Call.EXTRA_IS_BUSINESS_CALL)) {
+                boolean v = extras.getBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL);
+                Rlog.i(LOG_TAG, String.format("mIBCE: EXTRA_IS_BUSINESS_CALL=[%s]", v));
+                extras.putBoolean(android.telecom.Call.EXTRA_IS_BUSINESS_CALL, v);
+            }
+
+            if (extras.containsKey(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME)
+                    && !extras.containsKey(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME)) {
+                String v = extras.getString(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME);
+                Rlog.i(LOG_TAG, String.format("mIBCE: ASSERTED_DISPLAY_NAME=[%s]", v));
+                extras.putString(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME, v);
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
     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/imsphone/ImsPhoneFactory.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java
index 8f82328..0ae149c 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java
@@ -20,6 +20,7 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
 
 /**
@@ -35,10 +36,10 @@
      * @return the {@code ImsPhone} object
      */
     public static ImsPhone makePhone(Context context,
-            PhoneNotifier phoneNotifier, Phone defaultPhone) {
+            PhoneNotifier phoneNotifier, Phone defaultPhone, FeatureFlags featureFlags) {
 
         try {
-            return new ImsPhone(context, phoneNotifier, defaultPhone);
+            return new ImsPhone(context, phoneNotifier, defaultPhone, featureFlags);
         } catch (Exception e) {
             Rlog.e("VoltePhoneFactory", "makePhone", e);
             return null;
diff --git a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
index afb87dd..e1f6309 100644
--- a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
@@ -19,6 +19,7 @@
 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4;
 
 import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
 import android.os.SystemClock;
 import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.DataFailureCause;
@@ -62,8 +63,16 @@
 
     public static final int SIZE_LIMIT_HANDOVER_FAILURES = 15;
 
+    private final DefaultNetworkMonitor mDefaultNetworkMonitor;
+
     public DataCallSessionStats(Phone phone) {
         mPhone = phone;
+        mDefaultNetworkMonitor = PhoneFactory.getMetricsCollector().getDefaultNetworkMonitor();
+    }
+
+    private boolean isSystemDefaultNetworkMobile() {
+        NetworkCapabilities nc = mDefaultNetworkMonitor.getNetworkCapabilities();
+        return nc != null && nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
     }
 
     /** Creates a new ongoing atom when data call is set up. */
@@ -101,6 +110,9 @@
                     (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN)
                             ? 0
                             : ServiceStateStats.getBand(mPhone);
+            // Limitation: Will not capture IKE mobility between Backup Calling <-> WiFi Calling.
+            mDataCallSession.isIwlanCrossSim = currentRat == TelephonyManager.NETWORK_TYPE_IWLAN
+                    && isSystemDefaultNetworkMobile();
         }
 
         // only set if apn hasn't been set during setup
@@ -199,6 +211,8 @@
             if (mDataCallSession.ratAtEnd != currentRat) {
                 mDataCallSession.ratSwitchCount++;
                 mDataCallSession.ratAtEnd = currentRat;
+                mDataCallSession.isIwlanCrossSim = currentRat == TelephonyManager.NETWORK_TYPE_IWLAN
+                        && isSystemDefaultNetworkMobile();
             }
             // band may have changed even if RAT was the same
             mDataCallSession.bandAtEnd =
@@ -288,6 +302,7 @@
         copy.handoverFailureRat = Arrays.copyOf(call.handoverFailureRat,
                 call.handoverFailureRat.length);
         copy.isNonDds = call.isNonDds;
+        copy.isIwlanCrossSim = call.isIwlanCrossSim;
         return copy;
     }
 
@@ -313,6 +328,7 @@
         proto.handoverFailureCauses = new int[0];
         proto.handoverFailureRat = new int[0];
         proto.isNonDds = false;
+        proto.isIwlanCrossSim = false;
         return proto;
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
index 0fd97ba..387495e 100644
--- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
@@ -42,7 +42,7 @@
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.telephony.Rlog;
 
-import java.util.List;
+import java.util.Set;
 
 /**
  * Generates metrics related to data stall recovery events per phone ID for the pushed atom.
@@ -103,8 +103,9 @@
         dataNetworkController.registerDataNetworkControllerCallback(
                 new DataNetworkControllerCallback(mHandler::post) {
                 @Override
-                public void onInternetDataNetworkConnected(
-                        @NonNull List<DataNetwork> internetNetworks) {
+                public void onConnectedInternetDataNetworksChanged(
+                        @NonNull Set<DataNetwork> internetNetworks) {
+                    mIfaceName = null;
                     for (DataNetwork dataNetwork : internetNetworks) {
                         mIfaceName = dataNetwork.getLinkProperties().getInterfaceName();
                         break;
@@ -112,11 +113,6 @@
                 }
 
                 @Override
-                public void onInternetDataNetworkDisconnected() {
-                    mIfaceName = null;
-                }
-
-                @Override
                 public void onPhysicalLinkStatusChanged(@LinkStatus int status) {
                     mInternetLinkStatus = status;
                 }
diff --git a/src/java/com/android/internal/telephony/metrics/DefaultNetworkMonitor.java b/src/java/com/android/internal/telephony/metrics/DefaultNetworkMonitor.java
new file mode 100644
index 0000000..1dab10b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/DefaultNetworkMonitor.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 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.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.telephony.Rlog;
+
+/**
+ * Monitors the system default network registration and tracks the currently available
+ * {@link Network} and its {@link NetworkCapabilities}.
+ */
+public class DefaultNetworkMonitor extends Handler {
+
+    private static final String TAG = DefaultNetworkMonitor.class.getSimpleName();
+
+    @Nullable private NetworkCallback mDefaultNetworkCallback;
+    @Nullable private Network mNetwork;
+    @Nullable private NetworkCapabilities mNetworkCapabilities;
+
+    private final class DefaultNetworkCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            mNetwork = network;
+        }
+
+        @Override
+        public void onCapabilitiesChanged(@NonNull Network network,
+                @NonNull NetworkCapabilities nc) {
+            if (network == mNetwork) {
+                mNetworkCapabilities = nc;
+            }
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            mNetwork = null;
+            mNetworkCapabilities = null;
+        }
+    }
+
+    DefaultNetworkMonitor(@NonNull Context context, @NonNull FeatureFlags featureFlags) {
+        super(Looper.myLooper());
+        if (featureFlags.dataCallSessionStatsCapturesCrossSimCalling()) {
+            registerSystemDefaultNetworkCallback(context);
+        }
+    }
+
+    private void registerSystemDefaultNetworkCallback(@NonNull Context context) {
+        if (mDefaultNetworkCallback != null) {
+            return;
+        }
+        ConnectivityManager connectivityManager =
+                context.getSystemService(ConnectivityManager.class);
+        if (connectivityManager != null) {
+            mDefaultNetworkCallback = new DefaultNetworkCallback();
+            connectivityManager.registerSystemDefaultNetworkCallback(
+                    mDefaultNetworkCallback, this);
+        } else {
+            Rlog.e(TAG, "registerSystemDefaultNetworkCallback: ConnectivityManager is null!");
+        }
+    }
+
+    @Nullable public NetworkCapabilities getNetworkCapabilities() {
+        return mNetworkCapabilities;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/ImsStats.java b/src/java/com/android/internal/telephony/metrics/ImsStats.java
index 7059e43..d9994aa 100644
--- a/src/java/com/android/internal/telephony/metrics/ImsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/ImsStats.java
@@ -199,7 +199,7 @@
     @ImsRegistrationState private int mLastRegistrationState = REGISTRATION_STATE_NOT_REGISTERED;
 
     private long mLastTimestamp;
-    @Nullable private ImsRegistrationStats mLastRegistrationStats;
+    private ImsRegistrationStats mLastRegistrationStats;
     @TransportType int mLastTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
     // Available features are those reported by ImsService to be available for use.
     private MmTelCapabilities mLastAvailableFeatures = new MmTelCapabilities();
@@ -212,6 +212,10 @@
     public ImsStats(ImsPhone phone) {
         mPhone = phone;
         mStorage = PhoneFactory.getMetricsCollector().getAtomsStorage();
+
+        mLastRegistrationStats = getDefaultImsRegistrationStats();
+        updateImsRegistrationStats();
+        mLastTimestamp = getTimeMillis();
     }
 
     /**
@@ -223,40 +227,56 @@
     public synchronized void conclude() {
         long now = getTimeMillis();
 
-        // Currently not tracking time spent on registering.
-        if (mLastRegistrationState == REGISTRATION_STATE_REGISTERED) {
-            ImsRegistrationStats stats = copyOf(mLastRegistrationStats);
-            long duration = now - mLastTimestamp;
+        long duration = now - mLastTimestamp;
+        if (duration < MIN_REGISTRATION_DURATION_MILLIS) {
+            logw("conclude: discarding transient stats, duration=%d", duration);
+        } else {
+            ImsRegistrationStats stats = copyOfDimensionsOnly(mLastRegistrationStats);
 
-            if (duration < MIN_REGISTRATION_DURATION_MILLIS) {
-                logw("conclude: discarding transient stats, duration=%d", duration);
-            } else {
-                stats.registeredMillis = duration;
-
-                stats.voiceAvailableMillis =
-                        mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0;
-                stats.videoAvailableMillis =
-                        mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0;
-                stats.utAvailableMillis =
-                        mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0;
-                stats.smsAvailableMillis =
-                        mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0;
-
-                MmTelCapabilities lastCapableFeatures =
-                        stats.rat == TelephonyManager.NETWORK_TYPE_IWLAN
-                                ? mLastWlanCapableFeatures
-                                : mLastWwanCapableFeatures;
-                stats.voiceCapableMillis =
-                        lastCapableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0;
-                stats.videoCapableMillis =
-                        lastCapableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0;
-                stats.utCapableMillis =
-                        lastCapableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0;
-                stats.smsCapableMillis =
-                        lastCapableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0;
-
-                mStorage.addImsRegistrationStats(stats);
+            if (stats.rat == TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                logw("conclude: discarding UNKNOWN RAT, duration=%d", duration);
+                mLastTimestamp = now;
+                return;
             }
+
+            stats.registeredTimes = mLastRegistrationStats.registeredTimes;
+            // initialize registeredTimes after copying mLastRegistrationStats to be updated
+            mLastRegistrationStats.registeredTimes = 0;
+
+            switch (mLastRegistrationState) {
+                case REGISTRATION_STATE_REGISTERED:
+                    stats.registeredMillis = duration;
+
+                    stats.voiceAvailableMillis =
+                            mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0;
+                    stats.videoAvailableMillis =
+                            mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0;
+                    stats.utAvailableMillis =
+                            mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0;
+                    stats.smsAvailableMillis =
+                            mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0;
+
+                    MmTelCapabilities lastCapableFeatures =
+                            stats.rat == TelephonyManager.NETWORK_TYPE_IWLAN
+                                    ? mLastWlanCapableFeatures
+                                    : mLastWwanCapableFeatures;
+                    stats.voiceCapableMillis =
+                            lastCapableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0;
+                    stats.videoCapableMillis =
+                            lastCapableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0;
+                    stats.utCapableMillis =
+                            lastCapableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0;
+                    stats.smsCapableMillis =
+                            lastCapableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0;
+                    break;
+                case REGISTRATION_STATE_REGISTERING:
+                    stats.registeringMillis = duration;
+                    break;
+                case REGISTRATION_STATE_NOT_REGISTERED:
+                    stats.unregisteredMillis = duration;
+                    break;
+            }
+            mStorage.addImsRegistrationStats(stats);
         }
 
         mLastTimestamp = now;
@@ -273,13 +293,11 @@
                 (newRat == TelephonyManager.NETWORK_TYPE_IWLAN)
                         ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
                         : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-        if (mLastRegistrationStats != null) {
-            if (mLastRegistrationStats.rat != newRat) {
-                mLastRegistrationStats.rat = newRat;
-                ratChanged = true;
-            }
-            mLastRegistrationStats.isIwlanCrossSim = radioTech == REGISTRATION_TECH_CROSS_SIM;
+        if (mLastRegistrationStats.rat != newRat) {
+            mLastRegistrationStats.rat = newRat;
+            ratChanged = true;
         }
+        mLastRegistrationStats.isIwlanCrossSim = radioTech == REGISTRATION_TECH_CROSS_SIM;
 
         boolean voiceAvailableNow = capabilities.isCapable(CAPABILITY_TYPE_VOICE);
         boolean voiceAvailabilityChanged =
@@ -313,20 +331,28 @@
         conclude();
 
         mLastTransportType = imsRadioTech;
-        mLastRegistrationStats = getDefaultImsRegistrationStats();
+        updateImsRegistrationStats();
         mLastRegistrationStats.rat = convertTransportTypeToNetworkType(imsRadioTech);
         mLastRegistrationState = REGISTRATION_STATE_REGISTERING;
     }
 
     /** Updates the stats when IMS registration succeeds. */
     public synchronized void onImsRegistered(ImsRegistrationAttributes attributes) {
+        // Updates registered_times as soon as the UE is registered
+        if (mLastRegistrationState != REGISTRATION_STATE_REGISTERED) {
+            // RegistrationStats captures in every state. Changing REGISTERED state has to capture
+            // only once.
+            mLastRegistrationStats.registeredTimes = 1;
+        }
+
         conclude();
 
         mLastTransportType = attributes.getTransportType();
-        // NOTE: mLastRegistrationStats can be null (no registering phase).
-        if (mLastRegistrationStats == null) {
-            mLastRegistrationStats = getDefaultImsRegistrationStats();
+        // NOTE: status can be unregistered (no registering phase)
+        if (mLastRegistrationState == REGISTRATION_STATE_NOT_REGISTERED) {
+            updateImsRegistrationStats();
         }
+
         mLastRegistrationStats.rat =
                 convertTransportTypeToNetworkType(attributes.getTransportType());
         mLastRegistrationStats.isIwlanCrossSim = attributes.getRegistrationTechnology()
@@ -339,17 +365,16 @@
         conclude();
 
         // Generate end reason atom.
-        // NOTE: mLastRegistrationStats can be null (no registering phase).
         ImsRegistrationTermination termination = new ImsRegistrationTermination();
-        if (mLastRegistrationStats != null) {
+        if (mLastRegistrationState != REGISTRATION_STATE_NOT_REGISTERED) {
             termination.carrierId = mLastRegistrationStats.carrierId;
             termination.ratAtEnd = getRatAtEnd(mLastRegistrationStats.rat);
             termination.isIwlanCrossSim = mLastRegistrationStats.isIwlanCrossSim;
         } else {
+            // if the registration state is from unregistered to unregistered.
             termination.carrierId = mPhone.getDefaultPhone().getCarrierId();
-            // We cannot tell whether the registration was intended for WWAN or WLAN
-            termination.ratAtEnd = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         }
+        termination.ratAtEnd = getRatAtEnd(mLastRegistrationStats.rat);
         termination.isMultiSim = SimSlotState.isMultiSim();
         termination.setupFailed = (mLastRegistrationState != REGISTRATION_STATE_REGISTERED);
         termination.reasonCode = reasonInfo.getCode();
@@ -360,16 +385,20 @@
 
         // Reset state to unregistered.
         mLastRegistrationState = REGISTRATION_STATE_NOT_REGISTERED;
-        mLastRegistrationStats = null;
         mLastAvailableFeatures = new MmTelCapabilities();
     }
 
     /** Updates the RAT when service state changes. */
     public synchronized void onServiceStateChanged(ServiceState state) {
-        if (mLastTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
-                && mLastRegistrationStats != null) {
-            mLastRegistrationStats.rat =
-                    ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_PS);
+        conclude();
+
+        @NetworkType int newRat = state.getDataNetworkType();
+        MmTelCapabilities lastCapableFeatures = getLastCapableFeaturesForNetworkType(newRat);
+
+        if (lastCapableFeatures != null) {
+            mLastRegistrationStats.rat = newRat;
+        } else {
+            mLastRegistrationStats.rat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         }
     }
 
@@ -379,7 +408,7 @@
      */
     @NetworkType
     public synchronized int getImsVoiceRadioTech() {
-        if (mLastRegistrationStats == null
+        if (mLastRegistrationState == REGISTRATION_STATE_NOT_REGISTERED
                 || !mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE)) {
             return TelephonyManager.NETWORK_TYPE_UNKNOWN;
         }
@@ -413,11 +442,16 @@
     private ImsRegistrationStats getDefaultImsRegistrationStats() {
         Phone phone = mPhone.getDefaultPhone();
         ImsRegistrationStats stats = new ImsRegistrationStats();
-        stats.carrierId = phone.getCarrierId();
-        stats.simSlotIndex = phone.getPhoneId();
+        stats.rat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         return stats;
     }
 
+    private void updateImsRegistrationStats() {
+        Phone phone = mPhone.getDefaultPhone();
+        mLastRegistrationStats.carrierId = phone.getCarrierId();
+        mLastRegistrationStats.simSlotIndex = phone.getPhoneId();
+    }
+
     @Nullable
     private MmTelCapabilities getLastCapableFeaturesForTech(@ImsRegistrationTech int radioTech) {
         switch (radioTech) {
@@ -431,6 +465,18 @@
         }
     }
 
+    @Nullable
+    private MmTelCapabilities getLastCapableFeaturesForNetworkType(@NetworkType int netType) {
+        switch (netType) {
+            case TelephonyManager.NETWORK_TYPE_UNKNOWN:
+                return null;
+            case TelephonyManager.NETWORK_TYPE_IWLAN:
+                return mLastWlanCapableFeatures;
+            default:
+                return mLastWwanCapableFeatures;
+        }
+    }
+
     @NetworkType
     private int convertRegistrationTechToNetworkType(@ImsRegistrationTech int radioTech) {
         switch (radioTech) {
@@ -449,21 +495,12 @@
         }
     }
 
-    private static ImsRegistrationStats copyOf(ImsRegistrationStats source) {
+    private static ImsRegistrationStats copyOfDimensionsOnly(ImsRegistrationStats source) {
         ImsRegistrationStats dest = new ImsRegistrationStats();
 
         dest.carrierId = source.carrierId;
         dest.simSlotIndex = source.simSlotIndex;
         dest.rat = source.rat;
-        dest.registeredMillis = source.registeredMillis;
-        dest.voiceCapableMillis = source.voiceCapableMillis;
-        dest.voiceAvailableMillis = source.voiceAvailableMillis;
-        dest.smsCapableMillis = source.smsCapableMillis;
-        dest.smsAvailableMillis = source.smsAvailableMillis;
-        dest.videoCapableMillis = source.videoCapableMillis;
-        dest.videoAvailableMillis = source.videoAvailableMillis;
-        dest.utCapableMillis = source.utCapableMillis;
-        dest.utAvailableMillis = source.utAvailableMillis;
         dest.isIwlanCrossSim = source.isIwlanCrossSim;
 
         return dest;
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index c3f9820..a315f1e 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -53,7 +53,9 @@
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_UNKNOWN;
+import static com.android.internal.telephony.util.TelephonyUtils.IS_DEBUGGABLE;
 
+import android.annotation.NonNull;
 import android.app.StatsManager;
 import android.content.Context;
 import android.telephony.SubscriptionManager;
@@ -65,6 +67,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyStatsLog;
 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.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
@@ -97,6 +100,8 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.UceEventStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccSlot;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.telephony.Rlog;
 
@@ -134,34 +139,56 @@
             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.
      *
      * <p>Applies to metrics with duration fields. Currently used by voice call RAT usages.
      */
     private static final long MIN_CALLS_PER_BUCKET = DBG ? 0L : 5L;
 
-    /** Bucket size in milliseconds to round call durations into. */
+    /** Bucket size in milliseconds to round call durations info. */
     private static final long DURATION_BUCKET_MILLIS =
             DBG ? 2L * MILLIS_PER_SECOND : 5L * MILLIS_PER_MINUTE;
 
+    /**
+     * Sets smaller bucket size to round call durations for userdebug build.
+     *
+     * <p>Applies to certain atoms: CellularServiceState.
+     */
+    private static final long CELL_SERVICE_DURATION_BUCKET_MILLIS =
+            DBG || IS_DEBUGGABLE ? 2L * MILLIS_PER_SECOND : 5L * MILLIS_PER_MINUTE;
+
     private final PersistAtomsStorage mStorage;
     private final DeviceStateHelper mDeviceStateHelper;
     private final StatsManager mStatsManager;
+    private final VonrHelper mVonrHelper;
     private final AirplaneModeStats mAirplaneModeStats;
+    private final DefaultNetworkMonitor mDefaultNetworkMonitor;
     private final Set<DataCallSessionStats> mOngoingDataCallStats = ConcurrentHashMap.newKeySet();
     private static final Random sRandom = new Random();
 
-    public MetricsCollector(Context context) {
-        this(context, new PersistAtomsStorage(context), new DeviceStateHelper(context));
+    public MetricsCollector(Context context, @NonNull FeatureFlags featureFlags) {
+        this(context, new PersistAtomsStorage(context),
+                new DeviceStateHelper(context), new VonrHelper(featureFlags), featureFlags);
     }
 
     /** Allows dependency injection. Used during unit tests. */
     @VisibleForTesting
     public MetricsCollector(
-            Context context, PersistAtomsStorage storage, DeviceStateHelper deviceStateHelper) {
+            Context context, PersistAtomsStorage storage, DeviceStateHelper deviceStateHelper,
+                    VonrHelper vonrHelper, @NonNull FeatureFlags featureFlags) {
         mStorage = storage;
         mDeviceStateHelper = deviceStateHelper;
         mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
+        mVonrHelper = vonrHelper;
         if (mStatsManager != null) {
             // Most (but not all) of these are subject to cooldown specified by MIN_COOLDOWN_MILLIS.
             registerAtom(CELLULAR_DATA_SERVICE_SWITCH);
@@ -193,19 +220,20 @@
             registerAtom(GBA_EVENT);
             registerAtom(PER_SIM_STATUS);
             registerAtom(OUTGOING_SHORT_CODE_SMS);
+            registerAtom(EMERGENCY_NUMBERS_INFO);
             registerAtom(SATELLITE_CONTROLLER);
             registerAtom(SATELLITE_SESSION);
             registerAtom(SATELLITE_INCOMING_DATAGRAM);
             registerAtom(SATELLITE_OUTGOING_DATAGRAM);
             registerAtom(SATELLITE_PROVISION);
             registerAtom(SATELLITE_SOS_MESSAGE_RECOMMENDER);
-            registerAtom(EMERGENCY_NUMBERS_INFO);
             Rlog.d(TAG, "registered");
         } else {
             Rlog.e(TAG, "could not get StatsManager, atoms not registered");
         }
 
         mAirplaneModeStats = new AirplaneModeStats(context);
+        mDefaultNetworkMonitor = new DefaultNetworkMonitor(context, featureFlags);
     }
 
     /**
@@ -276,6 +304,8 @@
                 return pullPerSimStatus(data);
             case OUTGOING_SHORT_CODE_SMS:
                 return pullOutgoingShortCodeSms(data);
+            case EMERGENCY_NUMBERS_INFO:
+                return pullEmergencyNumbersInfo(data);
             case SATELLITE_CONTROLLER:
                 return pullSatelliteController(data);
             case SATELLITE_SESSION:
@@ -288,8 +318,6 @@
                 return pullSatelliteProvision(data);
             case SATELLITE_SOS_MESSAGE_RECOMMENDER:
                 return pullSatelliteSosMessageRecommender(data);
-            case EMERGENCY_NUMBERS_INFO:
-                return pullEmergencyNumbersInfo(data);
             default:
                 Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
                 return StatsManager.PULL_SKIP;
@@ -306,6 +334,11 @@
         return mDeviceStateHelper;
     }
 
+    /** Returns the {@link VonrHelper}. */
+    public VonrHelper getVonrHelper() {
+        return mVonrHelper;
+    }
+
     /** Updates duration segments and calls {@link PersistAtomsStorage#flushAtoms()}. */
     public void flushAtomsStorage() {
         concludeAll();
@@ -331,6 +364,10 @@
         mOngoingDataCallStats.remove(call);
     }
 
+    public DefaultNetworkMonitor getDefaultNetworkMonitor() {
+        return mDefaultNetworkMonitor;
+    }
+
     private void concludeDataCallSessionStats() {
         for (DataCallSessionStats stats : mOngoingDataCallStats) {
             stats.conclude();
@@ -383,7 +420,9 @@
                         SIM_SLOT_STATE,
                         state.numActiveSlots,
                         state.numActiveSims,
-                        state.numActiveEsims));
+                        state.numActiveEsims,
+                        state.numActiveEsimSlots,
+                        state.numActiveMepSlots));
         return StatsManager.PULL_SUCCESS;
     }
 
@@ -506,7 +545,7 @@
         // Include the latest durations
         concludeServiceStateStats();
         CellularServiceState[] persistAtoms =
-                mStorage.getCellularServiceStates(MIN_COOLDOWN_MILLIS);
+                mStorage.getCellularServiceStates(CELL_SERVICE_MIN_COOLDOWN_MILLIS);
         if (persistAtoms != null) {
             // list is already shuffled when instances were inserted
             Arrays.stream(persistAtoms)
@@ -572,9 +611,14 @@
         boolean hasDedicatedManagedProfileSub = Arrays.stream(phones)
                 .anyMatch(Phone::isManagedProfile);
 
+        UiccSlot[] slots = UiccController.getInstance().getUiccSlots();
+        int mepSupportedSlotCount = (int) Arrays.stream(slots)
+                .filter(UiccSlot::isMultipleEnabledProfileSupported)
+                .count();
+
         data.add(TelephonyStatsLog.buildStatsEvent(DEVICE_TELEPHONY_PROPERTIES, true,
                 isAutoDataSwitchOn, mStorage.getAutoDataSwitchToggleCount(),
-                hasDedicatedManagedProfileSub));
+                hasDedicatedManagedProfileSub, mepSupportedSlotCount));
         return StatsManager.PULL_SUCCESS;
     }
 
@@ -798,6 +842,21 @@
         }
     }
 
+    private int pullEmergencyNumbersInfo(List<StatsEvent> data) {
+        boolean isDataLogged = false;
+        for (Phone phone : getPhonesIfAny()) {
+            if (phone != null) {
+                EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker();
+                if (tracker != null) {
+                    EmergencyNumbersInfo[] numList = tracker.getEmergencyNumbersProtoArray();
+                    Arrays.stream(numList).forEach(number -> data.add(buildStatsEvent(number)));
+                    isDataLogged = true;
+                }
+            }
+        }
+        return isDataLogged ? StatsManager.PULL_SUCCESS : StatsManager.PULL_SKIP;
+    }
+
     private int pullSatelliteController(List<StatsEvent> data) {
         SatelliteController[] controllerAtoms =
                 mStorage.getSatelliteControllerStats(MIN_COOLDOWN_MILLIS);
@@ -878,21 +937,6 @@
         }
     }
 
-    private int pullEmergencyNumbersInfo(List<StatsEvent> data) {
-        boolean isDataLogged = false;
-        for (Phone phone : getPhonesIfAny()) {
-            if (phone != null) {
-                EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker();
-                if (tracker != null) {
-                    EmergencyNumbersInfo[] numList = tracker.getEmergencyNumbersProtoArray();
-                    Arrays.stream(numList).forEach(number -> data.add(buildStatsEvent(number)));
-                    isDataLogged = true;
-                }
-            }
-        }
-        return isDataLogged ? StatsManager.PULL_SUCCESS : StatsManager.PULL_SKIP;
-    }
-
     /** Registers a pulled atom ID {@code atomId}. */
     private void registerAtom(int atomId) {
         mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null,
@@ -921,7 +965,8 @@
                 state.simSlotIndex,
                 state.isMultiSim,
                 state.carrierId,
-                roundAndConvertMillisToSeconds(state.totalTimeMillis),
+                roundAndConvertMillisToSeconds(state.totalTimeMillis,
+                        CELL_SERVICE_DURATION_BUCKET_MILLIS),
                 state.isEmergencyOnly,
                 state.isInternetPdnUp,
                 state.foldState,
@@ -984,7 +1029,9 @@
                 session.handoverInProgress,
                 session.isIwlanCrossSimAtStart,
                 session.isIwlanCrossSimAtEnd,
-                session.isIwlanCrossSimAtConnected);
+                session.isIwlanCrossSimAtConnected,
+                session.vonrEnabled);
+
     }
 
     private static StatsEvent buildStatsEvent(IncomingSms sms) {
@@ -1077,7 +1124,8 @@
                 roundAndConvertMillisToSeconds(stats.utAvailableMillis),
                 roundAndConvertMillisToSeconds(stats.registeringMillis),
                 roundAndConvertMillisToSeconds(stats.unregisteredMillis),
-                stats.isIwlanCrossSim);
+                stats.isIwlanCrossSim,
+                stats.registeredTimes);
     }
 
     private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) {
@@ -1257,6 +1305,21 @@
                 shortCodeSms.shortCodeSmsCount);
     }
 
+    private static StatsEvent buildStatsEvent(EmergencyNumbersInfo emergencyNumber) {
+        return TelephonyStatsLog.buildStatsEvent(
+                EMERGENCY_NUMBERS_INFO,
+                emergencyNumber.isDbVersionIgnored,
+                emergencyNumber.assetVersion,
+                emergencyNumber.otaVersion,
+                emergencyNumber.number,
+                emergencyNumber.countryIso,
+                emergencyNumber.mnc,
+                emergencyNumber.route,
+                emergencyNumber.urns,
+                emergencyNumber.serviceCategories,
+                emergencyNumber.sources);
+    }
+
     private static StatsEvent buildStatsEvent(SatelliteController satelliteController) {
         return TelephonyStatsLog.buildStatsEvent(
                 SATELLITE_CONTROLLER,
@@ -1320,22 +1383,10 @@
                 stats.countOfTimerStarted,
                 stats.isImsRegistered,
                 stats.cellularServiceState,
-                stats.count);
-    }
-
-    private static StatsEvent buildStatsEvent(EmergencyNumbersInfo emergencyNumber) {
-        return TelephonyStatsLog.buildStatsEvent(
-                EMERGENCY_NUMBERS_INFO,
-                emergencyNumber.isDbVersionIgnored,
-                emergencyNumber.assetVersion,
-                emergencyNumber.otaVersion,
-                emergencyNumber.number,
-                emergencyNumber.countryIso,
-                emergencyNumber.mnc,
-                emergencyNumber.route,
-                emergencyNumber.urns,
-                emergencyNumber.serviceCategories,
-                emergencyNumber.sources);
+                stats.count,
+                stats.isMultiSim,
+                stats.recommendingHandoverType,
+                stats.isSatelliteAllowedInCurrentLocation);
     }
 
     /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
@@ -1352,8 +1403,15 @@
      * Rounds the duration and converts it from milliseconds to seconds.
      */
     private static int roundAndConvertMillisToSeconds(long valueMillis) {
-        long roundedValueMillis = Math.round((double) valueMillis / DURATION_BUCKET_MILLIS)
-                * DURATION_BUCKET_MILLIS;
+        return roundAndConvertMillisToSeconds(valueMillis, DURATION_BUCKET_MILLIS);
+    }
+
+    /**
+     * Rounds the duration and converts it from milliseconds to seconds.
+     */
+    private static int roundAndConvertMillisToSeconds(long valueMillis, long durationBucketSize) {
+        long roundedValueMillis = Math.round((double) valueMillis / durationBucketSize)
+                * durationBucketSize;
         return (int) (roundedValueMillis / MILLIS_PER_SECOND);
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/PerSimStatus.java b/src/java/com/android/internal/telephony/metrics/PerSimStatus.java
index a8c7421..70ff491 100644
--- a/src/java/com/android/internal/telephony/metrics/PerSimStatus.java
+++ b/src/java/com/android/internal/telephony/metrics/PerSimStatus.java
@@ -303,7 +303,7 @@
     }
 
     /** Returns true if VoNR is enabled */
-    private static boolean isVonrEnabled(Phone phone) {
+    static boolean isVonrEnabled(Phone phone) {
         TelephonyManager telephonyManager =
                 phone.getContext()
                         .getSystemService(TelephonyManager.class);
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index b186535..f3fe8fa 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -407,6 +407,9 @@
             existingStats.videoAvailableMillis += stats.videoAvailableMillis;
             existingStats.utCapableMillis += stats.utCapableMillis;
             existingStats.utAvailableMillis += stats.utAvailableMillis;
+            existingStats.registeringMillis += stats.registeringMillis;
+            existingStats.unregisteredMillis += stats.unregisteredMillis;
+            existingStats.registeredTimes += stats.registeredTimes;
             existingStats.lastUsedMillis = getWallTimeMillis();
         } else {
             stats.lastUsedMillis = getWallTimeMillis();
@@ -2071,7 +2074,9 @@
             if (stats.isDisplaySosMessageSent == key.isDisplaySosMessageSent
                     && stats.countOfTimerStarted == key.countOfTimerStarted
                     && stats.isImsRegistered == key.isImsRegistered
-                    && stats.cellularServiceState == key.cellularServiceState) {
+                    && stats.cellularServiceState == key.cellularServiceState
+                    && stats.isMultiSim == key.isMultiSim
+                    && stats.recommendingHandoverType == key.recommendingHandoverType) {
                 return stats;
             }
         }
@@ -2282,6 +2287,10 @@
                     normalizeDurationTo24H(stats[i].utCapableMillis, intervalMillis);
             stats[i].utAvailableMillis =
                     normalizeDurationTo24H(stats[i].utAvailableMillis, intervalMillis);
+            stats[i].registeringMillis =
+                    normalizeDurationTo24H(stats[i].registeringMillis, intervalMillis);
+            stats[i].unregisteredMillis =
+                    normalizeDurationTo24H(stats[i].unregisteredMillis, intervalMillis);
         }
         return stats;
     }
diff --git a/src/java/com/android/internal/telephony/metrics/RadioPowerStateStats.java b/src/java/com/android/internal/telephony/metrics/RadioPowerStateStats.java
new file mode 100644
index 0000000..f50c2b2
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/RadioPowerStateStats.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 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 com.android.internal.telephony.TelephonyStatsLog.CELLULAR_RADIO_POWER_STATE_CHANGED;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_RADIO_POWER_STATE_CHANGED__STATE__RADIO_POWER_STATE_OFF;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_RADIO_POWER_STATE_CHANGED__STATE__RADIO_POWER_STATE_ON;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_RADIO_POWER_STATE_CHANGED__STATE__RADIO_POWER_STATE_UNAVAILABLE;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_RADIO_POWER_STATE_CHANGED__STATE__RADIO_POWER_STATE_UNKNOWN;
+
+import android.telephony.Annotation;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.TelephonyStatsLog;
+
+/** Logs cellular radio power state changes to statsd */
+public class RadioPowerStateStats {
+
+    /** Logs cellular radio power state changes to statsd */
+    public static void onRadioStateChanged(@Annotation.RadioPowerState int state) {
+        TelephonyStatsLog.write(CELLULAR_RADIO_POWER_STATE_CHANGED,
+                radioPowerStateToProtoEnum(state));
+    }
+
+    private static int radioPowerStateToProtoEnum(@Annotation.RadioPowerState int state) {
+        switch (state) {
+            case TelephonyManager.RADIO_POWER_OFF:
+                return CELLULAR_RADIO_POWER_STATE_CHANGED__STATE__RADIO_POWER_STATE_OFF;
+            case TelephonyManager.RADIO_POWER_ON:
+                return CELLULAR_RADIO_POWER_STATE_CHANGED__STATE__RADIO_POWER_STATE_ON;
+            case TelephonyManager.RADIO_POWER_UNAVAILABLE:
+                return CELLULAR_RADIO_POWER_STATE_CHANGED__STATE__RADIO_POWER_STATE_UNAVAILABLE;
+            default:
+                return CELLULAR_RADIO_POWER_STATE_CHANGED__STATE__RADIO_POWER_STATE_UNKNOWN;
+        }
+    }
+
+}
diff --git a/src/java/com/android/internal/telephony/metrics/RcsStats.java b/src/java/com/android/internal/telephony/metrics/RcsStats.java
index 8d24def..20b23f9 100644
--- a/src/java/com/android/internal/telephony/metrics/RcsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/RcsStats.java
@@ -1029,8 +1029,11 @@
     }
 
     /** invalidated result when Request message is sent */
-    public synchronized void invalidatedMessageResult(int subId, String sipMessageMethod,
-            int sipMessageDirection, int messageError) {
+    public synchronized void invalidatedMessageResult(String callId, int subId,
+            String sipMessageMethod, int sipMessageDirection, int messageError) {
+        if (mSipMessage == null) {
+            mSipMessage = new SipMessageArray(sipMessageMethod, sipMessageDirection, callId);
+        }
         mSipMessage.addSipMessageStat(subId, sipMessageMethod, 0,
                 sipMessageDirection, messageError);
     }
diff --git a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
index 7ff370c..55eee1a 100644
--- a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
@@ -732,12 +732,19 @@
         private final int mCountOfTimerStarted;
         private final boolean mIsImsRegistered;
         private final int mCellularServiceState;
+        private final boolean mIsMultiSim;
+        private final int mRecommendingHandoverType;
+        private final boolean mIsSatelliteAllowedInCurrentLocation;
 
         private SatelliteSosMessageRecommenderParams(Builder builder) {
             this.mIsDisplaySosMessageSent = builder.mIsDisplaySosMessageSent;
             this.mCountOfTimerStarted = builder.mCountOfTimerStarted;
             this.mIsImsRegistered = builder.mIsImsRegistered;
             this.mCellularServiceState = builder.mCellularServiceState;
+            this.mIsMultiSim = builder.mIsMultiSim;
+            this.mRecommendingHandoverType = builder.mRecommendingHandoverType;
+            this.mIsSatelliteAllowedInCurrentLocation =
+                    builder.mIsSatelliteAllowedInCurrentLocation;
         }
 
         public boolean isDisplaySosMessageSent() {
@@ -756,6 +763,18 @@
             return mCellularServiceState;
         }
 
+        public boolean isMultiSim() {
+            return mIsMultiSim;
+        }
+
+        public int getRecommendingHandoverType() {
+            return mRecommendingHandoverType;
+        }
+
+        public boolean isSatelliteAllowedInCurrentLocation() {
+            return mIsSatelliteAllowedInCurrentLocation;
+        }
+
         /**
          * A builder class to create {@link SatelliteProvisionParams} data structure class
          */
@@ -764,6 +783,10 @@
             private int mCountOfTimerStarted = -1;
             private boolean mIsImsRegistered = false;
             private int mCellularServiceState = -1;
+            private boolean mIsMultiSim = false;
+            private int mRecommendingHandoverType = -1;
+            private boolean mIsSatelliteAllowedInCurrentLocation = false;
+
 
             /**
              * Sets resultCode value of {@link SatelliteSosMessageRecommender} atom
@@ -803,6 +826,34 @@
             }
 
             /**
+             * Sets isMultiSim value of {@link SatelliteSosMessageRecommender} atom
+             * then returns Builder class
+             */
+            public Builder setIsMultiSim(boolean isMultiSim) {
+                this.mIsMultiSim = isMultiSim;
+                return this;
+            }
+
+            /**
+             * Sets recommendingHandoverType value of {@link SatelliteSosMessageRecommender} atom
+             * then returns Builder class
+             */
+            public Builder setRecommendingHandoverType(int recommendingHandoverType) {
+                this.mRecommendingHandoverType = recommendingHandoverType;
+                return this;
+            }
+
+            /**
+             * Sets isSatelliteAllowedInCurrentLocation value of
+             * {@link SatelliteSosMessageRecommender} atom then returns Builder class.
+             */
+            public Builder setIsSatelliteAllowedInCurrentLocation(
+                    boolean satelliteAllowedInCurrentLocation) {
+                mIsSatelliteAllowedInCurrentLocation = satelliteAllowedInCurrentLocation;
+                return this;
+            }
+
+            /**
              * Returns SosMessageRecommenderParams, which contains whole component of
              * {@link SatelliteSosMessageRecommenderParams} atom
              */
@@ -818,7 +869,11 @@
                     + "isDisplaySosMessageSent=" + mIsDisplaySosMessageSent
                     + ", countOfTimerStarted=" + mCountOfTimerStarted
                     + ", isImsRegistered=" + mIsImsRegistered
-                    + ", cellularServiceState=" + mCellularServiceState + ")";
+                    + ", cellularServiceState=" + mCellularServiceState
+                    + ", isMultiSim=" + mIsMultiSim
+                    + ", recommendingHandoverType=" + mRecommendingHandoverType
+                    + ", isSatelliteAllowedInCurrentLocation="
+                    + mIsSatelliteAllowedInCurrentLocation + ")";
         }
     }
 
@@ -899,6 +954,9 @@
         proto.countOfTimerStarted = param.getCountOfTimerStarted();
         proto.isImsRegistered = param.isImsRegistered();
         proto.cellularServiceState = param.getCellularServiceState();
+        proto.isMultiSim = param.isMultiSim();
+        proto.recommendingHandoverType = param.getRecommendingHandoverType();
+        proto.isSatelliteAllowedInCurrentLocation = param.isSatelliteAllowedInCurrentLocation();
         proto.count = 1;
         mAtomsStorage.addSatelliteSosMessageRecommenderStats(proto);
     }
diff --git a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
index 400dfbb..d400c22 100644
--- a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
+++ b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
@@ -46,7 +46,7 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.telephony.Rlog;
 
-import java.util.List;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -60,6 +60,7 @@
     private final Phone mPhone;
     private final PersistAtomsStorage mStorage;
     private final DeviceStateHelper mDeviceStateHelper;
+    private boolean mExistAnyConnectedInternetPdn;
 
     public ServiceStateStats(Phone phone) {
         super(Runnable::run);
@@ -100,14 +101,14 @@
         mPhone.getDataNetworkController().registerDataNetworkControllerCallback(this);
     }
 
-    /** Updates service state when internet pdn gets connected. */
-    public void onInternetDataNetworkConnected(@NonNull List<DataNetwork> internetNetworks) {
-        onInternetDataNetworkChanged(true);
-    }
-
-    /** Updates service state when internet pdn gets disconnected. */
-    public void onInternetDataNetworkDisconnected() {
-        onInternetDataNetworkChanged(false);
+    /** Updates service state when internet pdn changed. */
+    @Override
+    public void onConnectedInternetDataNetworksChanged(@NonNull Set<DataNetwork> internetNetworks) {
+        boolean existAnyConnectedInternetPdn = !internetNetworks.isEmpty();
+        if (mExistAnyConnectedInternetPdn != existAnyConnectedInternetPdn) {
+            mExistAnyConnectedInternetPdn = existAnyConnectedInternetPdn;
+            onInternetDataNetworkChanged(mExistAnyConnectedInternetPdn);
+        }
     }
 
     /** Updates the current service state. */
diff --git a/src/java/com/android/internal/telephony/metrics/SimSlotState.java b/src/java/com/android/internal/telephony/metrics/SimSlotState.java
index f840894..59237e1 100644
--- a/src/java/com/android/internal/telephony/metrics/SimSlotState.java
+++ b/src/java/com/android/internal/telephony/metrics/SimSlotState.java
@@ -23,6 +23,9 @@
 import com.android.internal.telephony.uicc.UiccSlot;
 import com.android.telephony.Rlog;
 
+import java.util.Arrays;
+import java.util.Objects;
+
 /** Snapshots and stores the current SIM state. */
 public class SimSlotState {
     private static final String TAG = SimSlotState.class.getSimpleName();
@@ -30,31 +33,41 @@
     public final int numActiveSlots;
     public final int numActiveSims;
     public final int numActiveEsims;
+    public final int numActiveEsimSlots;
+    public final int numActiveMepSlots;
 
     /** Returns the current SIM state. */
     public static SimSlotState getCurrentState() {
         int numActiveSlots = 0;
         int numActiveSims = 0;
         int numActiveEsims = 0;
+        int numActiveEsimSlots = 0;
+        int numActiveMepSlots = 0;
         UiccController uiccController = UiccController.getInstance();
         // since we cannot hold lock insider UiccController, using getUiccSlots() for length only
         for (int i = 0; i < uiccController.getUiccSlots().length; i++) {
             UiccSlot slot = uiccController.getUiccSlot(i);
             if (slot != null && slot.isActive()) {
                 numActiveSlots++;
+                if (slot.isEuicc()) {
+                    numActiveEsimSlots++;
+                }
                 // avoid CardState.isCardPresent() since this should not include restricted cards
                 if (slot.getCardState() == CardState.CARDSTATE_PRESENT) {
                     if (slot.isEuicc()) {
                         // need to check active profiles besides the presence of eSIM cards
                         UiccCard card = slot.getUiccCard();
                         if (card != null) {
-                            // Check each port on the EuiccCard
-                            UiccPort[] uiccPorts = card.getUiccPortList();
-                            for (UiccPort port : uiccPorts) {
-                                if (port != null && port.getNumApplications() > 0) {
-                                    numActiveSims++;
-                                    numActiveEsims++;
-                                }
+                            int numActiveProfiles = (int) Arrays.stream(card.getUiccPortList())
+                                    .filter(Objects::nonNull)
+                                    .map(UiccPort::getUiccProfile)
+                                    .filter(Objects::nonNull)
+                                    .filter(profile -> profile.getNumApplications() > 0)
+                                    .count();
+                            numActiveSims += numActiveProfiles;
+                            numActiveEsims += numActiveProfiles;
+                            if (numActiveProfiles > 1) {
+                                numActiveMepSlots++;
                             }
                         }
                     } else {
@@ -64,13 +77,25 @@
                 }
             }
         }
-        return new SimSlotState(numActiveSlots, numActiveSims, numActiveEsims);
+        return new SimSlotState(
+                numActiveSlots,
+                numActiveSims,
+                numActiveEsims,
+                numActiveEsimSlots,
+                numActiveMepSlots);
     }
 
-    private SimSlotState(int numActiveSlots, int numActiveSims, int numActiveEsims) {
+    private SimSlotState(
+            int numActiveSlots,
+            int numActiveSims,
+            int numActiveEsims,
+            int numActiveEsimSlots,
+            int numActiveMepSlots) {
         this.numActiveSlots = numActiveSlots;
         this.numActiveSims = numActiveSims;
         this.numActiveEsims = numActiveEsims;
+        this.numActiveEsimSlots = numActiveEsimSlots;
+        this.numActiveMepSlots = numActiveMepSlots;
     }
 
     /** Returns whether the given phone is using a eSIM. */
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
index 8d755d0..9cf53c9 100644
--- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -36,6 +36,7 @@
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_GREAT;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.wifi.WifiInfo;
@@ -67,6 +68,9 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.analytics.TelephonyAnalytics;
+import com.android.internal.telephony.analytics.TelephonyAnalytics.CallAnalytics;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
@@ -154,6 +158,7 @@
      */
     private final VoiceCallRatTracker mRatUsage = new VoiceCallRatTracker();
 
+    private final @NonNull FeatureFlags mFlags;
     private final int mPhoneId;
     private final Phone mPhone;
 
@@ -163,10 +168,13 @@
     private final DeviceStateHelper mDeviceStateHelper =
             PhoneFactory.getMetricsCollector().getDeviceStateHelper();
 
-    public VoiceCallSessionStats(int phoneId, Phone phone) {
+    private final VonrHelper mVonrHelper =
+            PhoneFactory.getMetricsCollector().getVonrHelper();
+
+    public VoiceCallSessionStats(int phoneId, Phone phone, @NonNull FeatureFlags featureFlags) {
         mPhoneId = phoneId;
         mPhone = phone;
-
+        mFlags = featureFlags;
         DataConnectionStateTracker.getInstance(phoneId).start(phone);
     }
 
@@ -212,6 +220,19 @@
                     proto.disconnectExtraCode = conn.getPreciseDisconnectCause();
                     proto.disconnectExtraMessage = conn.getVendorDisconnectCause();
                     proto.callDuration = classifyCallDuration(conn.getDurationMillis());
+                    if (mPhone != null) {
+                        TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+                        if (telephonyAnalytics != null) {
+                            CallAnalytics callAnalytics = telephonyAnalytics.getCallAnalytics();
+                            if (callAnalytics != null) {
+                                callAnalytics.onCallTerminated(proto.isEmergency,
+                                        false /* isOverIms */,
+                                        getVoiceRatWithVoNRFix(mPhone, mPhone.getServiceState(),
+                                                proto.bearerAtEnd),
+                                        proto.simSlotIndex, proto.disconnectReasonCode);
+                            }
+                        }
+                    }
                     finishCall(id);
                 }
             }
@@ -442,6 +463,10 @@
         @VideoState int videoState = conn.getVideoState();
         VoiceCallSession proto = new VoiceCallSession();
 
+        if (mFlags.vonrEnabledMetric()) {
+            mVonrHelper.updateVonrEnabledState();
+        }
+
         proto.bearerAtStart = bearer;
         proto.bearerAtEnd = bearer;
         proto.direction = getDirection(conn);
@@ -541,6 +566,10 @@
         // Set device fold state
         proto.foldState = mDeviceStateHelper.getFoldState();
 
+        if (mFlags.vonrEnabledMetric()) {
+            proto.vonrEnabled = mVonrHelper.getVonrEnabled(mPhone.getSubId());
+        }
+
         mAtomsStorage.addVoiceCallSession(proto);
 
         // merge RAT usages to PersistPullers when the call session ends (i.e. no more active calls)
@@ -649,6 +678,18 @@
         proto.disconnectExtraCode = reasonInfo.mExtraCode;
         proto.disconnectExtraMessage = ImsStats.filterExtraMessage(reasonInfo.mExtraMessage);
         proto.callDuration = classifyCallDuration(durationMillis);
+        if (mPhone != null) {
+            TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
+            if (telephonyAnalytics != null) {
+                CallAnalytics callAnalytics = telephonyAnalytics.getCallAnalytics();
+                if (callAnalytics != null) {
+                    callAnalytics.onCallTerminated(proto.isEmergency, true /* isOverIms */ ,
+                            getVoiceRatWithVoNRFix(
+                                    mPhone, mPhone.getServiceState(), proto.bearerAtEnd),
+                            proto.simSlotIndex, proto.disconnectReasonCode);
+                }
+            }
+        }
         finishCall(id);
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/VonrHelper.java b/src/java/com/android/internal/telephony/metrics/VonrHelper.java
new file mode 100644
index 0000000..8f14a86
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/VonrHelper.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 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 com.android.internal.telephony.metrics.MetricsCollector.getPhonesIfAny;
+import static com.android.internal.telephony.metrics.PerSimStatus.isVonrEnabled;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.FeatureFlags;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Vonr state handler.
+ *
+ * <p>This class is instantiated in {@link MetricsCollector}.
+*/
+public class VonrHelper {
+    private final @NonNull FeatureFlags mFlags;
+
+    private Handler mHandler;
+    private Map<Integer, Boolean> mPhoneVonrState = new ConcurrentHashMap<>();
+
+    public VonrHelper(@NonNull FeatureFlags featureFlags) {
+        this.mFlags = featureFlags;
+        if (mFlags.vonrEnabledMetric()) {
+            HandlerThread mHandlerThread = new HandlerThread("VonrHelperThread");
+            mHandlerThread.start();
+            mHandler = new Handler(mHandlerThread.getLooper());
+        }
+    }
+
+    /** Update vonr_enabled state */
+    public void updateVonrEnabledState() {
+        if (mFlags.vonrEnabledMetric()) {
+            mHandler.post(mVonrRunnable);
+        }
+    }
+
+    @VisibleForTesting
+    protected Runnable mVonrRunnable =
+            new Runnable() {
+                @Override
+                public void run() {
+                    mPhoneVonrState.clear();
+                    for (Phone phone : getPhonesIfAny()) {
+                        mPhoneVonrState.put(phone.getSubId(), isVonrEnabled(phone));
+                    }
+                }
+            };
+
+    /** Get vonr_enabled per subId */
+    public boolean getVonrEnabled(int subId) {
+        if (mFlags.vonrEnabledMetric()) {
+            return mPhoneVonrState.getOrDefault(subId, false);
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/TEST_MAPPING b/src/java/com/android/internal/telephony/nitz/TEST_MAPPING
new file mode 100644
index 0000000..4063803
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksTelephonyTests",
+      "options": [
+        {
+          "include-filter": "com.android.internal.telephony.nitz."
+        }
+      ]
+    }
+  ]
+}
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index e7f09c2..877eaf1 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -16,6 +16,11 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF;
+
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Build;
@@ -26,6 +31,7 @@
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -43,9 +49,16 @@
     @NonNull private final PointingAppController mPointingAppController;
     @NonNull private final DatagramDispatcher mDatagramDispatcher;
     @NonNull private final DatagramReceiver mDatagramReceiver;
+
     public static final long MAX_DATAGRAM_ID = (long) Math.pow(2, 16);
     public static final int ROUNDING_UNIT = 10;
     public static final long SATELLITE_ALIGN_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
+    /** This type is used by CTS to override the satellite align timeout */
+    public static final int TIMEOUT_TYPE_ALIGN = 1;
+    /** This type is used by CTS to override the time to wait for connected state */
+    public static final int TIMEOUT_TYPE_DATAGRAM_WAIT_FOR_CONNECTED_STATE = 2;
+    /** This type is used by CTS to override the time to wait for response of the send request */
+    public static final int TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE = 3;
     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
     private static final boolean DEBUG = !"user".equals(Build.TYPE);
 
@@ -59,7 +72,7 @@
     @GuardedBy("mLock")
     private int mSendPendingCount = 0;
     @GuardedBy("mLock")
-    private int mSendErrorCode = SatelliteManager.SATELLITE_ERROR_NONE;
+    private int mSendErrorCode = SatelliteManager.SATELLITE_RESULT_SUCCESS;
     /** Variables used to update onReceiveDatagramStateChanged(). */
     @GuardedBy("mLock")
     private int mReceiveSubId;
@@ -69,11 +82,16 @@
     @GuardedBy("mLock")
     private int mReceivePendingCount = 0;
     @GuardedBy("mLock")
-    private int mReceiveErrorCode = SatelliteManager.SATELLITE_ERROR_NONE;
+    private int mReceiveErrorCode = SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
     private SatelliteDatagram mDemoModeDatagram;
     private boolean mIsDemoMode = false;
     private long mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
+    private long mDatagramWaitTimeForConnectedState;
+    private long mModemImageSwitchingDuration;
+    @GuardedBy("mLock")
+    @SatelliteManager.SatelliteModemState
+    private int mSatelltieModemState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
 
     /**
      * @return The singleton instance of DatagramController.
@@ -122,6 +140,9 @@
         // Create the DatagramReceiver singleton,
         // which is used to receive satellite datagrams.
         mDatagramReceiver = DatagramReceiver.make(mContext, looper, this);
+
+        mDatagramWaitTimeForConnectedState = getDatagramWaitForConnectedStateTimeoutMillis();
+        mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
     }
 
     /**
@@ -130,9 +151,9 @@
      * @param subId The subId of the subscription to register for incoming satellite datagrams.
      * @param callback The callback to handle incoming datagrams over satellite.
      *
-     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
-    @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId,
+    @SatelliteManager.SatelliteResult public int registerForSatelliteDatagram(int subId,
             @NonNull ISatelliteDatagramCallback callback) {
         return mDatagramReceiver.registerForSatelliteDatagram(subId, callback);
     }
@@ -159,7 +180,7 @@
      * long, SatelliteDatagram, int, Consumer)}
      *
      * @param subId The subId of the subscription used for receiving datagrams.
-     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
     public void pollPendingSatelliteDatagrams(int subId, @NonNull Consumer<Integer> callback) {
         mDatagramReceiver.pollPendingSatelliteDatagrams(subId, callback);
@@ -182,7 +203,7 @@
      *                 Datagram will be passed down to modem without any encoding or encryption.
      * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
      *                                 full screen mode.
-     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
     public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
@@ -267,13 +288,16 @@
      * @param state Current satellite modem state.
      */
     public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
+        synchronized (mLock) {
+            mSatelltieModemState = state;
+        }
         mDatagramDispatcher.onSatelliteModemStateChanged(state);
         mDatagramReceiver.onSatelliteModemStateChanged(state);
     }
 
-    void onDeviceAlignedWithSatellite(boolean isAligned) {
-        mDatagramDispatcher.onDeviceAlignedWithSatellite(isAligned);
-        mDatagramReceiver.onDeviceAlignedWithSatellite(isAligned);
+    void setDeviceAlignedWithSatellite(boolean isAligned) {
+        mDatagramDispatcher.setDeviceAlignedWithSatellite(isAligned);
+        mDatagramReceiver.setDeviceAlignedWithSatellite(isAligned);
     }
 
     @VisibleForTesting
@@ -284,17 +308,33 @@
         }
     }
 
+    /**
+     * Check if Telephony needs to wait for the modem satellite connected to a satellite network
+     * before transferring datagrams via satellite.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean needsWaitingForSatelliteConnected() {
+        synchronized (mLock) {
+            if (SatelliteController.getInstance().isSatelliteAttachRequired()
+                    && mSatelltieModemState != SATELLITE_MODEM_STATE_CONNECTED
+                    && mSatelltieModemState != SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING) {
+                return true;
+            }
+            return false;
+        }
+    }
+
     public boolean isSendingInIdleState() {
         synchronized (mLock) {
-            return mSendDatagramTransferState ==
-                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+            return (mSendDatagramTransferState
+                    == SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
         }
     }
 
     public boolean isPollingInIdleState() {
         synchronized (mLock) {
-            return mReceiveDatagramTransferState ==
-                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+            return (mReceiveDatagramTransferState
+                    == SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
         }
     }
 
@@ -336,21 +376,53 @@
         return mAlignTimeoutDuration;
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public long getDatagramWaitTimeForConnectedState() {
+        synchronized (mLock) {
+            if (mSatelltieModemState == SATELLITE_MODEM_STATE_OFF
+                    || mSatelltieModemState == SATELLITE_MODEM_STATE_IDLE) {
+                return (mDatagramWaitTimeForConnectedState + mModemImageSwitchingDuration);
+            }
+            return mDatagramWaitTimeForConnectedState;
+        }
+    }
+
     /**
-     * This API can be used by only CTS to update the timeout duration in milliseconds whether
-     * the device is aligned with the satellite for demo mode
+     * This API can be used by only CTS to timeout durations used by DatagramController module.
      *
      * @param timeoutMillis The timeout duration in millisecond.
      * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
      */
-    boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) {
+    boolean setDatagramControllerTimeoutDuration(
+            boolean reset, int timeoutType, long timeoutMillis) {
         if (!isMockModemAllowed()) {
-            loge("Updating align timeout duration is not allowed");
+            loge("Updating timeout duration is not allowed");
             return false;
         }
 
-        logd("setSatelliteDeviceAlignedTimeoutDuration: timeoutMillis=" + timeoutMillis);
-        mAlignTimeoutDuration = timeoutMillis;
+        logd("setDatagramControllerTimeoutDuration: timeoutMillis=" + timeoutMillis
+                + ", reset=" + reset + ", timeoutType=" + timeoutType);
+        if (timeoutType == TIMEOUT_TYPE_ALIGN) {
+            if (reset) {
+                mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
+            } else {
+                mAlignTimeoutDuration = timeoutMillis;
+            }
+        } else if (timeoutType == TIMEOUT_TYPE_DATAGRAM_WAIT_FOR_CONNECTED_STATE) {
+            if (reset) {
+                mDatagramWaitTimeForConnectedState =
+                        getDatagramWaitForConnectedStateTimeoutMillis();
+                mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
+            } else {
+                mDatagramWaitTimeForConnectedState = timeoutMillis;
+                mModemImageSwitchingDuration = 0;
+            }
+        } else if (timeoutType == TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE) {
+            mDatagramDispatcher.setWaitTimeForDatagramSendingResponse(reset, timeoutMillis);
+        } else {
+            loge("Invalid timeout type " + timeoutType);
+            return false;
+        }
         return true;
     }
 
@@ -369,6 +441,28 @@
         }
     }
 
+    private long getDatagramWaitForConnectedStateTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_datagram_wait_for_connected_state_timeout_millis);
+    }
+
+    private long getSatelliteModemImageSwitchingDurationMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_satellite_modem_image_switching_duration_millis);
+    }
+
+    /**
+     * This API can be used by only CTS to override the cached value for the device overlay config
+     * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
+     * outgoing satellite datagrams should be sent to modem in demo mode.
+     *
+     * @param shouldSendToModemInDemoMode Whether send datagram in demo mode should be sent to
+     * satellite modem or not.
+     */
+    void setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode) {
+        mDatagramDispatcher.setShouldSendDatagramToModemInDemoMode(shouldSendToModemInDemoMode);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
index 77b410d..5cac1dd 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -16,11 +16,16 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
 import static com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
@@ -30,6 +35,7 @@
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
@@ -39,6 +45,7 @@
 import java.util.LinkedHashMap;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 
@@ -51,6 +58,9 @@
     private static final int CMD_SEND_SATELLITE_DATAGRAM = 1;
     private static final int EVENT_SEND_SATELLITE_DATAGRAM_DONE = 2;
     private static final int EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT = 3;
+    private static final int EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT = 4;
+    private static final int EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT = 5;
+    private static final int EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE = 6;
 
     @NonNull private static DatagramDispatcher sInstance;
     @NonNull private final Context mContext;
@@ -63,6 +73,8 @@
 
     private static AtomicLong mNextDatagramId = new AtomicLong(0);
 
+    private AtomicBoolean mShouldSendDatagramToModemInDemoMode = null;
+
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
@@ -84,6 +96,8 @@
     private final LinkedHashMap<Long, SendSatelliteDatagramArgument>
             mPendingNonEmergencyDatagramsMap = new LinkedHashMap<>();
 
+    private long mWaitTimeForDatagramSendingResponse;
+
     /**
      * Create the DatagramDispatcher singleton instance.
      * @param context The Context to use to create the DatagramDispatcher.
@@ -117,6 +131,7 @@
         synchronized (mLock) {
             mSendingDatagramInProgress = false;
         }
+        mWaitTimeForDatagramSendingResponse = getWaitForDatagramSendingResponseTimeoutMillis();
     }
 
     private static final class DatagramDispatcherHandlerRequest {
@@ -187,59 +202,29 @@
                         (SendSatelliteDatagramArgument) request.argument;
                 onCompleted = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request);
 
-                if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
-                    SatelliteModemInterface.getInstance().sendSatelliteDatagram(argument.datagram,
-                            argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
-                            argument.needFullScreenPointingUI, onCompleted);
-                    break;
-                }
-
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.sendSatelliteDatagram(onCompleted, argument.datagram,
-                            argument.needFullScreenPointingUI);
-                } else {
-                    loge("sendSatelliteDatagram: No phone object");
-                    synchronized (mLock) {
-                        // Remove current datagram from pending map
-                        if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
-                            mPendingEmergencyDatagramsMap.remove(argument.datagramId);
-                        } else {
-                            mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
-                        }
-
-                        // Update send status
-                        mDatagramController.updateSendStatus(argument.subId,
-                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                                getPendingDatagramCount(),
-                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                        mDatagramController.updateSendStatus(argument.subId,
-                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                                0, SatelliteManager.SATELLITE_ERROR_NONE);
-
-                        // report phone == null case
-                        reportSendDatagramCompleted(argument,
-                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                        argument.callback.accept(
-                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-
-                        // Abort sending all the pending datagrams
-                        abortSendingPendingDatagrams(argument.subId,
-                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                synchronized (mLock) {
+                    if (mIsDemoMode && !shouldSendDatagramToModemInDemoMode()) {
+                        AsyncResult.forMessage(onCompleted, SATELLITE_RESULT_SUCCESS, null);
+                        onCompleted.sendToTarget();
+                    } else {
+                        SatelliteModemInterface.getInstance().sendSatelliteDatagram(
+                                argument.datagram,
+                                argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                                argument.needFullScreenPointingUI, onCompleted);
+                        startWaitForDatagramSendingResponseTimer(argument);
                     }
                 }
                 break;
             }
-
             case EVENT_SEND_SATELLITE_DATAGRAM_DONE: {
                 ar = (AsyncResult) msg.obj;
                 request = (DatagramDispatcherHandlerRequest) ar.userObj;
-                int error = SatelliteServiceUtils.getSatelliteError(ar, "sendSatelliteDatagram");
+                int error = SatelliteServiceUtils.getSatelliteError(ar, "sendDatagram");
                 SendSatelliteDatagramArgument argument =
                         (SendSatelliteDatagramArgument) request.argument;
 
                 synchronized (mLock) {
-                    if (mIsDemoMode && (error == SatelliteManager.SATELLITE_ERROR_NONE)) {
+                    if (mIsDemoMode && (error == SatelliteManager.SATELLITE_RESULT_SUCCESS)) {
                         if (argument.skipCheckingSatelliteAligned) {
                             logd("Satellite was already aligned. No need to check alignment again");
                         } else if (!mIsAligned) {
@@ -248,13 +233,25 @@
                             break;
                         }
                     }
-
                     logd("EVENT_SEND_SATELLITE_DATAGRAM_DONE error: " + error);
-                    // log metrics about the outgoing datagram
-                    reportSendDatagramCompleted(argument, error);
 
+                    /*
+                     * The response should be ignored if either of the following hold
+                     * 1) Framework has already received this response from the vendor service.
+                     * 2) Framework has timed out to wait for the response from vendor service for
+                     *    the send request.
+                     * 3) All pending send requests have been aborted due to some error.
+                     */
+                    if (!shouldProcessEventSendSatelliteDatagramDone(argument)) {
+                        logw("The message " + argument.datagramId + " was already processed");
+                        break;
+                    }
+
+                    stopWaitForDatagramSendingResponseTimer();
                     mSendingDatagramInProgress = false;
 
+                    // Log metrics about the outgoing datagram
+                    reportSendDatagramCompleted(argument, error);
                     // Remove current datagram from pending map.
                     if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
                         mPendingEmergencyDatagramsMap.remove(argument.datagramId);
@@ -262,7 +259,7 @@
                         mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
                     }
 
-                    if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    if (error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
                         // Update send status for current datagram
                         mDatagramController.updateSendStatus(argument.subId,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
@@ -278,7 +275,7 @@
                         } else {
                             mDatagramController.updateSendStatus(argument.subId,
                                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                                    0, SatelliteManager.SATELLITE_ERROR_NONE);
+                                    0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
                             // Send response for current datagram
                             argument.callback.accept(error);
                         }
@@ -289,7 +286,7 @@
                                 getPendingDatagramCount(), error);
                         mDatagramController.updateSendStatus(argument.subId,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                                0, SatelliteManager.SATELLITE_ERROR_NONE);
+                                0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
                         // Send response for current datagram
                         // after updating datagram transfer state internally.
                         argument.callback.accept(error);
@@ -297,17 +294,26 @@
                         mControllerMetricsStats.reportOutgoingDatagramFailCount(
                                 argument.datagramType);
                         abortSendingPendingDatagrams(argument.subId,
-                                SatelliteManager.SATELLITE_REQUEST_ABORTED);
+                                SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
                     }
                 }
                 break;
             }
 
+            case EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT:
+                handleEventWaitForDatagramSendingResponseTimedOut(
+                        (SendSatelliteDatagramArgument) msg.obj);
+                break;
+
             case EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT: {
                 handleEventSatelliteAlignedTimeout((DatagramDispatcherHandlerRequest) msg.obj);
                 break;
             }
 
+            case EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT:
+                handleEventDatagramWaitForConnectedStateTimedOut();
+                break;
+
             default:
                 logw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
                 break;
@@ -328,7 +334,7 @@
      *                 Datagram will be passed down to modem without any encoding or encryption.
      * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
      *                                 full screen mode.
-     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
     public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
@@ -350,14 +356,25 @@
                 mPendingNonEmergencyDatagramsMap.put(datagramId, datagramArgs);
             }
 
-            // Modem can be busy receiving datagrams, so send datagram only when modem is not busy.
-            if (!mSendingDatagramInProgress && mDatagramController.isPollingInIdleState()) {
+            if (mDatagramController.needsWaitingForSatelliteConnected()) {
+                logd("sendDatagram: wait for satellite connected");
+                mDatagramController.updateSendStatus(subId,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                        getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                startDatagramWaitForConnectedStateTimer();
+            } else if (!mSendingDatagramInProgress && mDatagramController.isPollingInIdleState()) {
+                // Modem can be busy receiving datagrams, so send datagram only when modem is
+                // not busy.
                 mSendingDatagramInProgress = true;
                 datagramArgs.setDatagramStartTime();
                 mDatagramController.updateSendStatus(subId,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
-                        getPendingDatagramCount(), SatelliteManager.SATELLITE_ERROR_NONE);
+                        getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
                 sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArgs, phone);
+            } else {
+                logd("sendDatagram: mSendingDatagramInProgress="
+                        + mSendingDatagramInProgress + ", isPollingInIdleState="
+                        + mDatagramController.isPollingInIdleState());
             }
         }
     }
@@ -378,7 +395,7 @@
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected void onDeviceAlignedWithSatellite(boolean isAligned) {
+    protected void setDeviceAlignedWithSatellite(boolean isAligned) {
         if (mIsDemoMode) {
             synchronized (mLock) {
                 mIsAligned = isAligned;
@@ -426,7 +443,7 @@
             @NonNull DatagramDispatcherHandlerRequest request) {
         SatelliteManager.SatelliteException exception =
                 new SatelliteManager.SatelliteException(
-                        SatelliteManager.SATELLITE_NOT_REACHABLE);
+                        SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
         Message message = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request);
         AsyncResult.forMessage(message, null, exception);
         message.sendToTarget();
@@ -474,7 +491,7 @@
             datagramArg.setDatagramStartTime();
             mDatagramController.updateSendStatus(datagramArg.subId,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
-                    getPendingDatagramCount(), SatelliteManager.SATELLITE_ERROR_NONE);
+                    getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
             sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArg, phone);
         }
     }
@@ -488,7 +505,7 @@
     @GuardedBy("mLock")
     private void sendErrorCodeAndCleanupPendingDatagrams(
             LinkedHashMap<Long, SendSatelliteDatagramArgument> pendingDatagramsMap,
-            @SatelliteManager.SatelliteError int errorCode) {
+            @SatelliteManager.SatelliteResult int errorCode) {
         if (pendingDatagramsMap.size() == 0) {
             return;
         }
@@ -515,7 +532,7 @@
      */
     @GuardedBy("mLock")
     private void abortSendingPendingDatagrams(int subId,
-            @SatelliteManager.SatelliteError int errorCode) {
+            @SatelliteManager.SatelliteResult int errorCode) {
         logd("abortSendingPendingDatagrams()");
         sendErrorCodeAndCleanupPendingDatagrams(mPendingEmergencyDatagramsMap, errorCode);
         sendErrorCodeAndCleanupPendingDatagrams(mPendingNonEmergencyDatagramsMap, errorCode);
@@ -525,9 +542,10 @@
      * Return pending datagram count
      * @return pending datagram count
      */
-    @GuardedBy("mLock")
-    private int getPendingDatagramCount() {
-        return mPendingEmergencyDatagramsMap.size() + mPendingNonEmergencyDatagramsMap.size();
+    public int getPendingDatagramCount() {
+        synchronized (mLock) {
+            return mPendingEmergencyDatagramsMap.size() + mPendingNonEmergencyDatagramsMap.size();
+        }
     }
 
     /**
@@ -545,7 +563,7 @@
     }
 
     private void reportSendDatagramCompleted(@NonNull SendSatelliteDatagramArgument argument,
-            @NonNull @SatelliteManager.SatelliteError int resultCode) {
+            @NonNull @SatelliteManager.SatelliteResult int resultCode) {
         SatelliteStats.getInstance().onSatelliteOutgoingDatagramMetrics(
                 new SatelliteStats.SatelliteOutgoingDatagramParams.Builder()
                         .setDatagramType(argument.datagramType)
@@ -579,6 +597,12 @@
             } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_IDLE) {
                 sendPendingDatagrams();
             }
+
+            if (state == SATELLITE_MODEM_STATE_CONNECTED
+                    && isDatagramWaitForConnectedStateTimerStarted()) {
+                stopDatagramWaitForConnectedStateTimer();
+                sendPendingDatagrams();
+            }
         }
     }
 
@@ -589,20 +613,182 @@
             mDatagramController.updateSendStatus(
                     SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                    getPendingDatagramCount(), SatelliteManager.SATELLITE_REQUEST_ABORTED);
+                    getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
         }
         mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                0, SatelliteManager.SATELLITE_ERROR_NONE);
+                0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
         abortSendingPendingDatagrams(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                SatelliteManager.SATELLITE_REQUEST_ABORTED);
+                SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
 
         stopSatelliteAlignedTimer();
+        stopDatagramWaitForConnectedStateTimer();
+        stopWaitForDatagramSendingResponseTimer();
         mIsDemoMode = false;
         mSendSatelliteDatagramRequest = null;
         mIsAligned = false;
     }
 
+    private void startDatagramWaitForConnectedStateTimer() {
+        if (isDatagramWaitForConnectedStateTimerStarted()) {
+            logd("DatagramWaitForConnectedStateTimer is already started");
+            return;
+        }
+        sendMessageDelayed(obtainMessage(
+                        EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT),
+                mDatagramController.getDatagramWaitTimeForConnectedState());
+    }
+
+    private void stopDatagramWaitForConnectedStateTimer() {
+        removeMessages(EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isDatagramWaitForConnectedStateTimerStarted() {
+        return hasMessages(EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT);
+    }
+
+    /**
+     * This API is used by CTS tests to override the mWaitTimeForDatagramSendingResponse.
+     */
+    void setWaitTimeForDatagramSendingResponse(boolean reset, long timeoutMillis) {
+        if (reset) {
+            mWaitTimeForDatagramSendingResponse = getWaitForDatagramSendingResponseTimeoutMillis();
+        } else {
+            mWaitTimeForDatagramSendingResponse = timeoutMillis;
+        }
+    }
+
+    private void startWaitForDatagramSendingResponseTimer(
+            @NonNull SendSatelliteDatagramArgument argument) {
+        if (hasMessages(EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT)) {
+            logd("WaitForDatagramSendingResponseTimer was already started");
+            return;
+        }
+        sendMessageDelayed(obtainMessage(
+                EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT, argument),
+                mWaitTimeForDatagramSendingResponse);
+    }
+
+    private void stopWaitForDatagramSendingResponseTimer() {
+        removeMessages(EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT);
+    }
+
+    private void handleEventDatagramWaitForConnectedStateTimedOut() {
+        logw("Timed out to wait for satellite connected before sending datagrams");
+        synchronized (mLock) {
+            // Update send status
+            mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                    getPendingDatagramCount(),
+                    SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+            mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                    0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
+            abortSendingPendingDatagrams(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+        }
+    }
+
+    private boolean shouldSendDatagramToModemInDemoMode() {
+        if (mShouldSendDatagramToModemInDemoMode != null) {
+            return mShouldSendDatagramToModemInDemoMode.get();
+        }
+
+        try {
+            mShouldSendDatagramToModemInDemoMode = new AtomicBoolean(
+                    mContext.getResources().getBoolean(
+                            R.bool.config_send_satellite_datagram_to_modem_in_demo_mode));
+            return mShouldSendDatagramToModemInDemoMode.get();
+
+        } catch (Resources.NotFoundException ex) {
+            loge("shouldSendDatagramToModemInDemoMode: id= "
+                    + R.bool.config_send_satellite_datagram_to_modem_in_demo_mode + ", ex=" + ex);
+            return false;
+        }
+    }
+
+    private long getWaitForDatagramSendingResponseTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_wait_for_datagram_sending_response_timeout_millis);
+    }
+
+    private boolean shouldProcessEventSendSatelliteDatagramDone(
+            @NonNull SendSatelliteDatagramArgument argument) {
+        synchronized (mLock) {
+            if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+                return mPendingEmergencyDatagramsMap.containsKey(argument.datagramId);
+            } else {
+                return mPendingNonEmergencyDatagramsMap.containsKey(argument.datagramId);
+            }
+        }
+    }
+
+    private void handleEventWaitForDatagramSendingResponseTimedOut(
+            @NonNull SendSatelliteDatagramArgument argument) {
+        synchronized (mLock) {
+            logw("Timed out to wait for the response of the request to send the datagram "
+                    + argument.datagramId);
+
+            // Ask vendor service to abort all datagram-sending requests
+            SatelliteModemInterface.getInstance().abortSendingSatelliteDatagrams(
+                    obtainMessage(EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE, argument));
+            mSendingDatagramInProgress = false;
+
+            // Update send status
+            mDatagramController.updateSendStatus(argument.subId,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                    getPendingDatagramCount(), SATELLITE_RESULT_MODEM_TIMEOUT);
+            mDatagramController.updateSendStatus(argument.subId,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                    0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
+            // Send response for current datagram after updating datagram transfer state
+            // internally.
+            argument.callback.accept(SATELLITE_RESULT_MODEM_TIMEOUT);
+
+            // Log metrics about the outgoing datagram
+            reportSendDatagramCompleted(argument, SATELLITE_RESULT_MODEM_TIMEOUT);
+            mControllerMetricsStats.reportOutgoingDatagramFailCount(argument.datagramType);
+            // Remove current datagram from pending map.
+            if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+                mPendingEmergencyDatagramsMap.remove(argument.datagramId);
+            } else {
+                mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
+            }
+
+            // Abort sending all the pending datagrams
+            abortSendingPendingDatagrams(argument.subId,
+                    SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
+        }
+    }
+
+    /**
+     * This API can be used by only CTS to override the cached value for the device overlay config
+     * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
+     * outgoing satellite datagrams should be sent to modem in demo mode.
+     *
+     * @param shouldSendToModemInDemoMode Whether send datagram in demo mode should be sent to
+     * satellite modem or not. If it is null, the cache will be cleared.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected void setShouldSendDatagramToModemInDemoMode(
+            @Nullable Boolean shouldSendToModemInDemoMode) {
+        logd("setShouldSendDatagramToModemInDemoMode(" + (shouldSendToModemInDemoMode == null
+                ? "null" : shouldSendToModemInDemoMode) + ")");
+
+        if (shouldSendToModemInDemoMode == null) {
+            mShouldSendDatagramToModemInDemoMode = null;
+        } else {
+            if (mShouldSendDatagramToModemInDemoMode == null) {
+                mShouldSendDatagramToModemInDemoMode = new AtomicBoolean(
+                        shouldSendToModemInDemoMode);
+            } else {
+                mShouldSendDatagramToModemInDemoMode.set(shouldSendToModemInDemoMode);
+            }
+        }
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
index 06eede1..c267fd7 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+
 import static com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT;
 
 import android.annotation.NonNull;
@@ -65,6 +67,7 @@
     private static final int CMD_POLL_PENDING_SATELLITE_DATAGRAMS = 1;
     private static final int EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE = 2;
     private static final int EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT = 3;
+    private static final int EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT = 4;
 
     /** Key used to read/write satellite datagramId in shared preferences. */
     private static final String SATELLITE_DATAGRAM_ID_KEY = "satellite_datagram_id_key";
@@ -82,7 +85,10 @@
     private boolean mIsDemoMode = false;
     @GuardedBy("mLock")
     private boolean mIsAligned = false;
-    private DatagramReceiverHandlerRequest mPollPendingSatelliteDatagramsRequest = null;
+    @Nullable
+    private DatagramReceiverHandlerRequest mDemoPollPendingSatelliteDatagramsRequest = null;
+    @Nullable
+    private DatagramReceiverHandlerRequest mPendingPollSatelliteDatagramsRequest = null;
     private final Object mLock = new Object();
 
     /**
@@ -139,12 +145,6 @@
         } catch (Exception e) {
             loge("Cannot get default shared preferences: " + e);
         }
-
-        if ((mSharedPreferences != null) &&
-                (!mSharedPreferences.contains(SATELLITE_DATAGRAM_ID_KEY))) {
-            mSharedPreferences.edit().putLong(SATELLITE_DATAGRAM_ID_KEY, mNextDatagramId.get())
-                    .commit();
-        }
     }
 
     private static final class DatagramReceiverHandlerRequest {
@@ -335,11 +335,11 @@
                     if (pendingCount <= 0 && satelliteDatagram == null) {
                         sInstance.mDatagramController.updateReceiveStatus(mSubId,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE,
-                                pendingCount, SatelliteManager.SATELLITE_ERROR_NONE);
+                                pendingCount, SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     } else if (satelliteDatagram != null) {
                         sInstance.mDatagramController.updateReceiveStatus(mSubId,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS,
-                                pendingCount, SatelliteManager.SATELLITE_ERROR_NONE);
+                                pendingCount, SatelliteManager.SATELLITE_RESULT_SUCCESS);
 
                         long datagramId = getDatagramId();
                         sInstance.mPendingAckCountHashMap.put(datagramId, getNumOfListeners());
@@ -356,13 +356,13 @@
                         });
 
                         sInstance.mControllerMetricsStats.reportIncomingDatagramCount(
-                                SatelliteManager.SATELLITE_ERROR_NONE);
+                                SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     }
 
                     if (pendingCount <= 0) {
                         sInstance.mDatagramController.updateReceiveStatus(mSubId,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                                pendingCount, SatelliteManager.SATELLITE_ERROR_NONE);
+                                pendingCount, SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     } else {
                         // Poll for pending datagrams
                         IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
@@ -378,7 +378,7 @@
 
                     // Send the captured data about incoming datagram to metric
                     sInstance.reportMetrics(
-                            satelliteDatagram, SatelliteManager.SATELLITE_ERROR_NONE);
+                            satelliteDatagram, SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     break;
                 }
 
@@ -392,6 +392,10 @@
 
                 case EVENT_RECEIVED_ACK: {
                     DatagramRetryArgument argument = (DatagramRetryArgument) msg.obj;
+                    if (!sInstance.mPendingAckCountHashMap.containsKey(argument.datagramId)) {
+                        logd("The datagram " + argument.datagramId + " should have been deleted.");
+                        return;
+                    }
                     int pendingAckCount = sInstance.mPendingAckCountHashMap
                             .get(argument.datagramId);
                     pendingAckCount -= 1;
@@ -424,35 +428,7 @@
                 request = (DatagramReceiverHandlerRequest) msg.obj;
                 onCompleted =
                         obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, request);
-
-                if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
-                    SatelliteModemInterface.getInstance()
-                            .pollPendingSatelliteDatagrams(onCompleted);
-                    break;
-                }
-
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.pollPendingSatelliteDatagrams(onCompleted);
-                } else {
-                    loge("pollPendingSatelliteDatagrams: No phone object");
-                    mDatagramController.updateReceiveStatus(request.subId,
-                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
-                            mDatagramController.getReceivePendingCount(),
-                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-
-                    mDatagramController.updateReceiveStatus(request.subId,
-                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                            mDatagramController.getReceivePendingCount(),
-                            SatelliteManager.SATELLITE_ERROR_NONE);
-
-                    reportMetrics(null, SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                    mControllerMetricsStats.reportIncomingDatagramCount(
-                                    SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                    // Send response for current request
-                    ((Consumer<Integer>) request.argument)
-                            .accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                }
+                SatelliteModemInterface.getInstance().pollPendingSatelliteDatagrams(onCompleted);
                 break;
             }
 
@@ -462,7 +438,7 @@
                 int error = SatelliteServiceUtils.getSatelliteError(ar,
                         "pollPendingSatelliteDatagrams");
 
-                if (mIsDemoMode && error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                if (mIsDemoMode && error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
                     SatelliteDatagram datagram = mDatagramController.getDemoModeDatagram();
                     final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(
                             request.subId, mContext);
@@ -476,12 +452,12 @@
                                 ar);
                         listenerHandler.sendMessage(message);
                     } else {
-                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                        error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     }
                 }
 
                 logd("EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE error: " + error);
-                if (error != SatelliteManager.SATELLITE_ERROR_NONE) {
+                if (error != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
                     mDatagramController.updateReceiveStatus(request.subId,
                             SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
                             mDatagramController.getReceivePendingCount(), error);
@@ -489,7 +465,7 @@
                     mDatagramController.updateReceiveStatus(request.subId,
                             SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                             mDatagramController.getReceivePendingCount(),
-                            SatelliteManager.SATELLITE_ERROR_NONE);
+                            SatelliteManager.SATELLITE_RESULT_SUCCESS);
 
                     reportMetrics(null, error);
                     mControllerMetricsStats.reportIncomingDatagramCount(error);
@@ -503,6 +479,14 @@
                 handleEventSatelliteAlignedTimeout((DatagramReceiverHandlerRequest) msg.obj);
                 break;
             }
+
+            case EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT:
+                handleEventDatagramWaitForConnectedStateTimedOut();
+                break;
+
+            default:
+                logw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
+                break;
         }
     }
 
@@ -512,12 +496,12 @@
      * @param subId The subId of the subscription to register for incoming satellite datagrams.
      * @param callback The callback to handle incoming datagrams over satellite.
      *
-     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
-    @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId,
+    @SatelliteManager.SatelliteResult public int registerForSatelliteDatagram(int subId,
             @NonNull ISatelliteDatagramCallback callback) {
-        if (!SatelliteController.getInstance().isSatelliteSupported()) {
-            return SatelliteManager.SATELLITE_NOT_SUPPORTED;
+        if (!SatelliteController.getInstance().isSatelliteSupportedViaOem()) {
+            return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
 
         final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
@@ -526,20 +510,14 @@
         if (satelliteDatagramListenerHandler == null) {
             satelliteDatagramListenerHandler = new SatelliteDatagramListenerHandler(
                     mLooper, validSubId);
-            if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
-                SatelliteModemInterface.getInstance().registerForSatelliteDatagramsReceived(
-                        satelliteDatagramListenerHandler,
-                        SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, null);
-            } else {
-                Phone phone = SatelliteServiceUtils.getPhone();
-                phone.registerForSatelliteDatagramsReceived(satelliteDatagramListenerHandler,
-                        SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, null);
-            }
+            SatelliteModemInterface.getInstance().registerForSatelliteDatagramsReceived(
+                    satelliteDatagramListenerHandler,
+                    SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, null);
         }
 
         satelliteDatagramListenerHandler.addListener(callback);
         mSatelliteDatagramListenerHandlers.put(validSubId, satelliteDatagramListenerHandler);
-        return SatelliteManager.SATELLITE_ERROR_NONE;
+        return SatelliteManager.SATELLITE_RESULT_SUCCESS;
     }
 
     /**
@@ -560,15 +538,8 @@
 
             if (!handler.hasListeners()) {
                 mSatelliteDatagramListenerHandlers.remove(validSubId);
-                if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
-                    SatelliteModemInterface.getInstance()
-                            .unregisterForSatelliteDatagramsReceived(handler);
-                } else {
-                    Phone phone = SatelliteServiceUtils.getPhone();
-                    if (phone != null) {
-                        phone.unregisterForSatelliteDatagramsReceived(handler);
-                    }
-                }
+                SatelliteModemInterface.getInstance().unregisterForSatelliteDatagramsReceived(
+                        handler);
             }
         }
     }
@@ -582,32 +553,65 @@
      * #onSatelliteDatagramReceived(long, SatelliteDatagram, int, Consumer)}
      *
      * @param subId The subId of the subscription used for receiving datagrams.
-     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
     public void pollPendingSatelliteDatagrams(int subId, @NonNull Consumer<Integer> callback) {
         if (!mDatagramController.isPollingInIdleState()) {
             // Poll request should be sent to satellite modem only when it is free.
             logd("pollPendingSatelliteDatagrams: satellite modem is busy receiving datagrams.");
-            callback.accept(SatelliteManager.SATELLITE_MODEM_BUSY);
+            callback.accept(SatelliteManager.SATELLITE_RESULT_MODEM_BUSY);
             return;
         }
-
         pollPendingSatelliteDatagramsInternal(subId, callback);
     }
 
+    private void handleSatelliteConnectedEvent() {
+        synchronized (mLock) {
+            if (isDatagramWaitForConnectedStateTimerStarted()) {
+                stopDatagramWaitForConnectedStateTimer();
+                if (mPendingPollSatelliteDatagramsRequest == null) {
+                    loge("handleSatelliteConnectedEvent: mPendingPollSatelliteDatagramsRequest is"
+                            + " null");
+                    return;
+                }
+
+                Consumer<Integer> callback =
+                        (Consumer<Integer>) mPendingPollSatelliteDatagramsRequest.argument;
+                pollPendingSatelliteDatagramsInternal(
+                        mPendingPollSatelliteDatagramsRequest.subId, callback);
+                mPendingPollSatelliteDatagramsRequest = null;
+            }
+        }
+    }
+
     private void pollPendingSatelliteDatagramsInternal(int subId,
             @NonNull Consumer<Integer> callback) {
         if (!mDatagramController.isSendingInIdleState()) {
             // Poll request should be sent to satellite modem only when it is free.
-            logd("pollPendingSatelliteDatagrams: satellite modem is busy sending datagrams.");
-            callback.accept(SatelliteManager.SATELLITE_MODEM_BUSY);
+            logd("pollPendingSatelliteDatagramsInternal: satellite modem is busy sending "
+                    + "datagrams.");
+            callback.accept(SatelliteManager.SATELLITE_RESULT_MODEM_BUSY);
+            return;
+        }
+
+        if (mDatagramController.needsWaitingForSatelliteConnected()) {
+            logd("pollPendingSatelliteDatagramsInternal: wait for satellite connected");
+            synchronized (mLock) {
+                mPendingPollSatelliteDatagramsRequest = new DatagramReceiverHandlerRequest(
+                        callback, SatelliteServiceUtils.getPhone(), subId);
+                mDatagramController.updateReceiveStatus(subId,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                        mDatagramController.getReceivePendingCount(),
+                        SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                startDatagramWaitForConnectedStateTimer();
+            }
             return;
         }
 
         mDatagramController.updateReceiveStatus(subId,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
                 mDatagramController.getReceivePendingCount(),
-                SatelliteManager.SATELLITE_ERROR_NONE);
+                SatelliteManager.SATELLITE_RESULT_SUCCESS);
         mDatagramTransferStartTime = System.currentTimeMillis();
         Phone phone = SatelliteServiceUtils.getPhone();
 
@@ -641,6 +645,8 @@
                     || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
                 logd("onSatelliteModemStateChanged: cleaning up resources");
                 cleanUpResources();
+            } else if (state == SATELLITE_MODEM_STATE_CONNECTED) {
+                handleSatelliteConnectedEvent();
             }
         }
     }
@@ -649,31 +655,40 @@
     private void cleanupDemoModeResources() {
         if (isSatelliteAlignedTimerStarted()) {
             stopSatelliteAlignedTimer();
-            if (mPollPendingSatelliteDatagramsRequest == null) {
+            if (mDemoPollPendingSatelliteDatagramsRequest == null) {
                 loge("Satellite aligned timer was started "
-                        + "but mPollPendingSatelliteDatagramsRequest is null");
+                        + "but mDemoPollPendingSatelliteDatagramsRequest is null");
             } else {
                 Consumer<Integer> callback =
-                        (Consumer<Integer>) mPollPendingSatelliteDatagramsRequest.argument;
-                callback.accept(SatelliteManager.SATELLITE_REQUEST_ABORTED);
+                        (Consumer<Integer>) mDemoPollPendingSatelliteDatagramsRequest.argument;
+                callback.accept(SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
             }
         }
         mIsDemoMode = false;
-        mPollPendingSatelliteDatagramsRequest = null;
+        mDemoPollPendingSatelliteDatagramsRequest = null;
         mIsAligned = false;
     }
 
     @GuardedBy("mLock")
     private void cleanUpResources() {
+        synchronized (mLock) {
+            if (mPendingPollSatelliteDatagramsRequest != null) {
+                Consumer<Integer> callback =
+                        (Consumer<Integer>) mPendingPollSatelliteDatagramsRequest.argument;
+                callback.accept(SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
+                mPendingPollSatelliteDatagramsRequest = null;
+            }
+            stopDatagramWaitForConnectedStateTimer();
+        }
         if (mDatagramController.isReceivingDatagrams()) {
             mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
                     mDatagramController.getReceivePendingCount(),
-                    SatelliteManager.SATELLITE_REQUEST_ABORTED);
+                    SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
         }
         mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0,
-                SatelliteManager.SATELLITE_ERROR_NONE);
+                SatelliteManager.SATELLITE_RESULT_SUCCESS);
         cleanupDemoModeResources();
     }
 
@@ -695,7 +710,7 @@
 
     /** Report incoming datagram related metrics */
     private void reportMetrics(@Nullable SatelliteDatagram satelliteDatagram,
-            @NonNull @SatelliteManager.SatelliteError int resultCode) {
+            @NonNull @SatelliteManager.SatelliteResult int resultCode) {
         int datagramSizeRoundedBytes = -1;
         int datagramTransferTime = 0;
 
@@ -728,7 +743,7 @@
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected void onDeviceAlignedWithSatellite(boolean isAligned) {
+    protected void setDeviceAlignedWithSatellite(boolean isAligned) {
         if (mIsDemoMode) {
             synchronized (mLock) {
                 mIsAligned = isAligned;
@@ -742,7 +757,7 @@
             logd("Satellite aligned timer was already started");
             return;
         }
-        mPollPendingSatelliteDatagramsRequest = request;
+        mDemoPollPendingSatelliteDatagramsRequest = request;
         sendMessageDelayed(
                 obtainMessage(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT, request),
                 getSatelliteAlignedTimeoutDuration());
@@ -757,13 +772,14 @@
         if (isSatelliteAlignedTimerStarted()) {
             stopSatelliteAlignedTimer();
 
-            if (mPollPendingSatelliteDatagramsRequest == null) {
-                loge("handleSatelliteAlignedTimer: mPollPendingSatelliteDatagramsRequest is null");
+            if (mDemoPollPendingSatelliteDatagramsRequest == null) {
+                loge("handleSatelliteAlignedTimer: mDemoPollPendingSatelliteDatagramsRequest "
+                        + "is null");
             } else {
                 Message message = obtainMessage(
                         EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE,
-                        mPollPendingSatelliteDatagramsRequest);
-                mPollPendingSatelliteDatagramsRequest = null;
+                        mDemoPollPendingSatelliteDatagramsRequest);
+                mDemoPollPendingSatelliteDatagramsRequest = null;
                 AsyncResult.forMessage(message, null, null);
                 message.sendToTarget();
             }
@@ -773,7 +789,7 @@
     private void handleEventSatelliteAlignedTimeout(DatagramReceiverHandlerRequest request) {
         SatelliteManager.SatelliteException exception =
                 new SatelliteManager.SatelliteException(
-                        SatelliteManager.SATELLITE_NOT_REACHABLE);
+                        SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
         Message message = obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, request);
         AsyncResult.forMessage(message, null, exception);
         message.sendToTarget();
@@ -787,6 +803,55 @@
         removeMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT);
     }
 
+    private void startDatagramWaitForConnectedStateTimer() {
+        if (isDatagramWaitForConnectedStateTimerStarted()) {
+            logd("DatagramWaitForConnectedStateTimer is already started");
+            return;
+        }
+        sendMessageDelayed(obtainMessage(
+                        EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT),
+                mDatagramController.getDatagramWaitTimeForConnectedState());
+    }
+
+    private void stopDatagramWaitForConnectedStateTimer() {
+        removeMessages(EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isDatagramWaitForConnectedStateTimerStarted() {
+        return hasMessages(EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT);
+    }
+
+    private void handleEventDatagramWaitForConnectedStateTimedOut() {
+        synchronized (mLock) {
+            if (mPendingPollSatelliteDatagramsRequest == null) {
+                logw("handleEventDatagramWaitForConnectedStateTimedOut: "
+                        + "mPendingPollSatelliteDatagramsRequest is null");
+                return;
+            }
+
+            logw("Timed out to wait for satellite connected before polling datagrams");
+            mDatagramController.updateReceiveStatus(mPendingPollSatelliteDatagramsRequest.subId,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
+                    mDatagramController.getReceivePendingCount(),
+                    SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+
+            mDatagramController.updateReceiveStatus(mPendingPollSatelliteDatagramsRequest.subId,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                    mDatagramController.getReceivePendingCount(),
+                    SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
+            reportMetrics(null, SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+            mControllerMetricsStats.reportIncomingDatagramCount(
+                    SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+
+            Consumer<Integer> callback =
+                    (Consumer<Integer>) mPendingPollSatelliteDatagramsRequest.argument;
+            callback.accept(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+            mPendingPollSatelliteDatagramsRequest = null;
+        }
+    }
+
     /**
      * Destroys this DatagramDispatcher. Used for tearing down static resources during testing.
      */
@@ -802,4 +867,8 @@
     private static void loge(@NonNull String log) {
         Rlog.e(TAG, log);
     }
+
+    private static void logw(@NonNull String log) {
+        Rlog.w(TAG, log);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
index 5d4e5dd..add01c0 100644
--- a/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
+++ b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
@@ -40,7 +40,7 @@
     public static void resolveNtnCapability(
             @NonNull NetworkRegistrationInfo networkRegistrationInfo, int subId) {
         SatelliteController satelliteController = SatelliteController.getInstance();
-        List<String> satellitePlmnList = satelliteController.getSatellitePlmnList();
+        List<String> satellitePlmnList = satelliteController.getSatellitePlmnsForCarrier(subId);
         String registeredPlmn = networkRegistrationInfo.getRegisteredPlmn();
         for (String satellitePlmn : satellitePlmnList) {
             if (TextUtils.equals(satellitePlmn, registeredPlmn)) {
diff --git a/src/java/com/android/internal/telephony/satellite/PointingAppController.java b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
index 9dba6f2..878ee96 100644
--- a/src/java/com/android/internal/telephony/satellite/PointingAppController.java
+++ b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
@@ -16,12 +16,16 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Handler;
@@ -38,9 +42,9 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.Phone;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
@@ -57,9 +61,12 @@
     private static PointingAppController sInstance;
     @NonNull private final Context mContext;
     private boolean mStartedSatelliteTransmissionUpdates;
+    private boolean mLastNeedFullScreenPointingUI;
+    private boolean mListenerForPointingUIRegistered;
     @NonNull private String mPointingUiPackageName = "";
     @NonNull private String mPointingUiClassName = "";
-
+    @NonNull private ActivityManager mActivityManager;
+    @NonNull public UidImportanceListener mUidImportanceListener = new UidImportanceListener();
     /**
      * Map key: subId, value: SatelliteTransmissionUpdateHandler to notify registrants.
      */
@@ -97,6 +104,9 @@
     public PointingAppController(@NonNull Context context) {
         mContext = context;
         mStartedSatelliteTransmissionUpdates = false;
+        mLastNeedFullScreenPointingUI = false;
+        mListenerForPointingUIRegistered = false;
+        mActivityManager = mContext.getSystemService(ActivityManager.class);
     }
 
     /**
@@ -119,6 +129,36 @@
         return mStartedSatelliteTransmissionUpdates;
     }
 
+    /**
+     * Get the flag mStartedSatelliteTransmissionUpdates
+     * @return returns mStartedSatelliteTransmissionUpdates
+     */
+    @VisibleForTesting
+    public boolean getLastNeedFullScreenPointingUI() {
+        return mLastNeedFullScreenPointingUI;
+    }
+
+    /**
+     * Listener for handling pointing UI App in the event of crash
+     */
+    @VisibleForTesting
+    public class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
+        @Override
+        public void onUidImportance(int uid, int importance) {
+            if (importance != IMPORTANCE_GONE) return;
+            final PackageManager pm = mContext.getPackageManager();
+            final String[] callerPackages = pm.getPackagesForUid(uid);
+            String pointingUiPackage = getPointingUiPackageName();
+
+            if (callerPackages != null) {
+                if (Arrays.stream(callerPackages).anyMatch(pointingUiPackage::contains)) {
+                    logd("Restarting pointingUI");
+                    startPointingUI(mLastNeedFullScreenPointingUI);
+                }
+            }
+        }
+    }
+
     private static final class DatagramTransferStateHandlerRequest {
         public int datagramTransferState;
         public int pendingCount;
@@ -235,31 +275,24 @@
      * Register to start receiving updates for satellite position and datagram transfer state
      * @param subId The subId of the subscription to register for receiving the updates.
      * @param callback The callback to notify of satellite transmission updates.
-     * @param phone The Phone object to unregister for receiving the updates.
      */
     public void registerForSatelliteTransmissionUpdates(int subId,
-            ISatelliteTransmissionUpdateCallback callback, Phone phone) {
+            ISatelliteTransmissionUpdateCallback callback) {
         SatelliteTransmissionUpdateHandler handler =
                 mSatelliteTransmissionUpdateHandlers.get(subId);
         if (handler != null) {
             handler.addListener(callback);
-            return;
         } else {
             handler = new SatelliteTransmissionUpdateHandler(Looper.getMainLooper());
             handler.addListener(callback);
             mSatelliteTransmissionUpdateHandlers.put(subId, handler);
-            if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
-                SatelliteModemInterface.getInstance().registerForSatellitePositionInfoChanged(
-                        handler, SatelliteTransmissionUpdateHandler.EVENT_POSITION_INFO_CHANGED,
-                        null);
-                SatelliteModemInterface.getInstance().registerForDatagramTransferStateChanged(
-                        handler,
-                        SatelliteTransmissionUpdateHandler.EVENT_DATAGRAM_TRANSFER_STATE_CHANGED,
-                        null);
-            } else {
-                phone.registerForSatellitePositionInfoChanged(handler,
-                        SatelliteTransmissionUpdateHandler.EVENT_POSITION_INFO_CHANGED, null);
-            }
+            SatelliteModemInterface.getInstance().registerForSatellitePositionInfoChanged(
+                    handler, SatelliteTransmissionUpdateHandler.EVENT_POSITION_INFO_CHANGED,
+                    null);
+            SatelliteModemInterface.getInstance().registerForDatagramTransferStateChanged(
+                    handler,
+                    SatelliteTransmissionUpdateHandler.EVENT_DATAGRAM_TRANSFER_STATE_CHANGED,
+                    null);
         }
     }
 
@@ -269,33 +302,23 @@
      * @param subId The subId of the subscription to unregister for receiving the updates.
      * @param result The callback to get the error code in case of failure
      * @param callback The callback that was passed to {@link
-     * #registerForSatelliteTransmissionUpdates(int, ISatelliteTransmissionUpdateCallback, Phone)}.
-     * @param phone The Phone object to unregister for receiving the updates
+     * #registerForSatelliteTransmissionUpdates(int, ISatelliteTransmissionUpdateCallback)}.
      */
     public void unregisterForSatelliteTransmissionUpdates(int subId, Consumer<Integer> result,
-            ISatelliteTransmissionUpdateCallback callback, Phone phone) {
+            ISatelliteTransmissionUpdateCallback callback) {
         SatelliteTransmissionUpdateHandler handler =
                 mSatelliteTransmissionUpdateHandlers.get(subId);
         if (handler != null) {
             handler.removeListener(callback);
             if (handler.hasListeners()) {
-                result.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+                result.accept(SatelliteManager.SATELLITE_RESULT_SUCCESS);
                 return;
             }
-
             mSatelliteTransmissionUpdateHandlers.remove(subId);
-            if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
-                SatelliteModemInterface.getInstance().unregisterForSatellitePositionInfoChanged(
-                        handler);
-                SatelliteModemInterface.getInstance().unregisterForDatagramTransferStateChanged(
-                        handler);
-            } else {
-                if (phone == null) {
-                    result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                    return;
-                }
-                phone.unregisterForSatellitePositionInfoChanged(handler);
-            }
+
+            SatelliteModemInterface satelliteModemInterface = SatelliteModemInterface.getInstance();
+            satelliteModemInterface.unregisterForSatellitePositionInfoChanged(handler);
+            satelliteModemInterface.unregisterForDatagramTransferStateChanged(handler);
         }
     }
 
@@ -307,28 +330,16 @@
      * {@link android.telephony.satellite.SatelliteTransmissionUpdateCallback
      * #onSatellitePositionChanged(pointingInfo)}.
      */
-    public void startSatelliteTransmissionUpdates(@NonNull Message message, @Nullable Phone phone) {
+    public void startSatelliteTransmissionUpdates(@NonNull Message message) {
         if (mStartedSatelliteTransmissionUpdates) {
             logd("startSatelliteTransmissionUpdates: already started");
             AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException(
-                    SatelliteManager.SATELLITE_ERROR_NONE));
+                    SatelliteManager.SATELLITE_RESULT_SUCCESS));
             message.sendToTarget();
             return;
         }
-        if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
-            SatelliteModemInterface.getInstance().startSendingSatellitePointingInfo(message);
-            mStartedSatelliteTransmissionUpdates = true;
-            return;
-        }
-        if (phone != null) {
-            phone.startSatellitePositionUpdates(message);
-            mStartedSatelliteTransmissionUpdates = true;
-        } else {
-            loge("startSatelliteTransmissionUpdates: No phone object");
-            AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException(
-                    SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
-            message.sendToTarget();
-        }
+        SatelliteModemInterface.getInstance().startSendingSatellitePointingInfo(message);
+        mStartedSatelliteTransmissionUpdates = true;
     }
 
     /**
@@ -336,20 +347,9 @@
      * Reset the flag mStartedSatelliteTransmissionUpdates
      * This can be called by the pointing UI when the user stops pointing to the satellite.
      */
-    public void stopSatelliteTransmissionUpdates(@NonNull Message message, @Nullable Phone phone) {
+    public void stopSatelliteTransmissionUpdates(@NonNull Message message) {
         setStartedSatelliteTransmissionUpdates(false);
-        if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
-            SatelliteModemInterface.getInstance().stopSendingSatellitePointingInfo(message);
-            return;
-        }
-        if (phone != null) {
-            phone.stopSatellitePositionUpdates(message);
-        } else {
-            loge("startSatelliteTransmissionUpdates: No phone object");
-            AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException(
-                    SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
-            message.sendToTarget();
-        }
+        SatelliteModemInterface.getInstance().stopSendingSatellitePointingInfo(message);
     }
 
     /**
@@ -376,15 +376,32 @@
             loge("startPointingUI: launchIntent is null");
             return;
         }
+        logd("startPointingUI: needFullScreenPointingUI: " + needFullScreenPointingUI);
         launchIntent.putExtra("needFullScreen", needFullScreenPointingUI);
 
         try {
+            if (!mListenerForPointingUIRegistered) {
+                mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
+                        IMPORTANCE_GONE);
+                mListenerForPointingUIRegistered = true;
+            }
+            mLastNeedFullScreenPointingUI = needFullScreenPointingUI;
             mContext.startActivity(launchIntent);
         } catch (ActivityNotFoundException ex) {
             loge("startPointingUI: Pointing UI app activity is not found, ex=" + ex);
         }
     }
 
+    /**
+     * Remove the Importance Listener For Pointing UI App once the satellite is disabled
+     */
+    public void removeListenerForPointingUI() {
+        if (mListenerForPointingUIRegistered) {
+            mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
+            mListenerForPointingUIRegistered = false;
+        }
+    }
+
     public void updateSendDatagramTransferState(int subId,
             @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
             int sendPendingCount, int errorCode) {
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 957b152..0d3973d 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -16,9 +16,31 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.provider.Settings.ACTION_SATELLITE_SETTING;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL;
+import static android.telephony.SubscriptionManager.SATELLITE_ATTACH_ENABLED_FOR_CARRIER;
+import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
+import static android.telephony.SubscriptionManager.isValidSubscriptionId;
+import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
+import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
+import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
+import static android.telephony.satellite.SatelliteManager.KEY_NTN_SIGNAL_STRENGTH;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
 import android.annotation.ArrayRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -28,6 +50,7 @@
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.nfc.NfcAdapter;
 import android.os.AsyncResult;
@@ -45,29 +68,40 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
+import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.satellite.INtnSignalStrengthCallback;
+import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
-import android.telephony.satellite.ISatelliteStateCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.uwb.UwbManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.DeviceStateMonitor;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
@@ -75,12 +109,17 @@
 import com.android.internal.util.FunctionalUtils;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 
 /**
@@ -98,6 +137,16 @@
     /** Value to pass for the setting key SATELLITE_MODE_ENABLED, enabled = 1, disabled = 0 */
     public static final int SATELLITE_MODE_ENABLED_TRUE = 1;
     public static final int SATELLITE_MODE_ENABLED_FALSE = 0;
+    public static final int INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE = -1;
+    /**
+     * This is used by CTS to override the timeout duration to wait for the response of the request
+     * to enable satellite.
+     */
+    public static final int TIMEOUT_TYPE_WAIT_FOR_SATELLITE_ENABLING_RESPONSE = 1;
+
+    /** Key used to read/write OEM-enabled satellite provision status in shared preferences. */
+    private static final String OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY =
+            "oem_enabled_satellite_provision_status_key";
 
     /** Message codes used in handleMessage() */
     //TODO: Move the Commands and events related to position updates to PointingAppController
@@ -127,6 +176,17 @@
     private static final int EVENT_SATELLITE_PROVISION_STATE_CHANGED = 26;
     private static final int EVENT_PENDING_DATAGRAMS = 27;
     private static final int EVENT_SATELLITE_MODEM_STATE_CHANGED = 28;
+    private static final int EVENT_SET_SATELLITE_PLMN_INFO_DONE = 29;
+    private static final int CMD_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE = 30;
+    private static final int EVENT_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE_DONE = 31;
+    private static final int CMD_REQUEST_NTN_SIGNAL_STRENGTH = 32;
+    private static final int EVENT_REQUEST_NTN_SIGNAL_STRENGTH_DONE = 33;
+    private static final int EVENT_NTN_SIGNAL_STRENGTH_CHANGED = 34;
+    private static final int CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING = 35;
+    private static final int EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE = 36;
+    private static final int EVENT_SERVICE_STATE_CHANGED = 37;
+    private static final int EVENT_SATELLITE_CAPABILITIES_CHANGED = 38;
+    private static final int EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT = 39;
 
     @NonNull private static SatelliteController sInstance;
     @NonNull private final Context mContext;
@@ -136,13 +196,14 @@
     @NonNull private final DatagramController mDatagramController;
     @NonNull private final ControllerMetricsStats mControllerMetricsStats;
     @NonNull private final ProvisionMetricsStats mProvisionMetricsStats;
-    private SharedPreferences mSharedPreferences = null;
+    @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
     private final CommandsInterface mCi;
-    private ContentResolver mContentResolver = null;
+    private ContentResolver mContentResolver;
+    private final DeviceStateMonitor mDSM;
 
     private final Object mRadioStateLock = new Object();
 
-    /** Flags to indicate whether the resepective radio is enabled */
+    /** Flags to indicate whether the respective radio is enabled */
     @GuardedBy("mRadioStateLock")
     private boolean mBTStateEnabled = false;
     @GuardedBy("mRadioStateLock")
@@ -172,16 +233,14 @@
 
     private final AtomicBoolean mRegisteredForProvisionStateChangedWithSatelliteService =
             new AtomicBoolean(false);
-    private final AtomicBoolean mRegisteredForProvisionStateChangedWithPhone =
-            new AtomicBoolean(false);
     private final AtomicBoolean mRegisteredForPendingDatagramCountWithSatelliteService =
             new AtomicBoolean(false);
-    private final AtomicBoolean mRegisteredForPendingDatagramCountWithPhone =
-            new AtomicBoolean(false);
     private final AtomicBoolean mRegisteredForSatelliteModemStateChangedWithSatelliteService =
             new AtomicBoolean(false);
-    private final AtomicBoolean mRegisteredForSatelliteModemStateChangedWithPhone =
+    private final AtomicBoolean mRegisteredForNtnSignalStrengthChanged = new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForSatelliteCapabilitiesChanged =
             new AtomicBoolean(false);
+    private final AtomicBoolean mShouldReportNtnSignalStrength = new AtomicBoolean(false);
     /**
      * Map key: subId, value: callback to get error code of the provision request.
      */
@@ -193,6 +252,18 @@
      */
     private final ConcurrentHashMap<IBinder, ISatelliteProvisionStateCallback>
             mSatelliteProvisionStateChangedListeners = new ConcurrentHashMap<>();
+    /**
+     * Map key: binder of the callback, value: callback to receive non-terrestrial signal strength
+     * state changed events.
+     */
+    private final ConcurrentHashMap<IBinder, INtnSignalStrengthCallback>
+            mNtnSignalStrengthChangedListeners = new ConcurrentHashMap<>();
+    /**
+     * Map key: binder of the callback, value: callback to receive satellite capabilities changed
+     * events.
+     */
+    private final ConcurrentHashMap<IBinder, ISatelliteCapabilitiesCallback>
+            mSatelliteCapabilitiesChangedListeners = new ConcurrentHashMap<>();
     private final Object mIsSatelliteSupportedLock = new Object();
     @GuardedBy("mIsSatelliteSupportedLock")
     private Boolean mIsSatelliteSupported = null;
@@ -201,31 +272,98 @@
     @GuardedBy("mIsSatelliteEnabledLock")
     private Boolean mIsSatelliteEnabled = null;
     private boolean mIsRadioOn = false;
-    private final Object mIsSatelliteProvisionedLock = new Object();
-    @GuardedBy("mIsSatelliteProvisionedLock")
-    private Boolean mIsSatelliteProvisioned = null;
+    private final Object mSatelliteViaOemProvisionLock = new Object();
+    @GuardedBy("mSatelliteViaOemProvisionLock")
+    private Boolean mIsSatelliteViaOemProvisioned = null;
+    @GuardedBy("mSatelliteViaOemProvisionLock")
+    private Boolean mOverriddenIsSatelliteViaOemProvisioned = null;
     private final Object mSatelliteCapabilitiesLock = new Object();
     @GuardedBy("mSatelliteCapabilitiesLock")
     private SatelliteCapabilities mSatelliteCapabilities;
     private final Object mNeedsSatellitePointingLock = new Object();
     @GuardedBy("mNeedsSatellitePointingLock")
     private boolean mNeedsSatellitePointing = false;
+    private final Object mNtnSignalsStrengthLock = new Object();
+    @GuardedBy("mNtnSignalsStrengthLock")
+    private NtnSignalStrength mNtnSignalStrength =
+            new NtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE);
     /** Key: subId, value: (key: PLMN, value: set of
      * {@link android.telephony.NetworkRegistrationInfo.ServiceType})
      */
     @GuardedBy("mSupportedSatelliteServicesLock")
-    @NonNull private final Map<Integer, Map<String, Set<Integer>>> mSupportedSatelliteServices =
-            new HashMap<>();
+    @NonNull private final Map<Integer, Map<String, Set<Integer>>>
+            mSatelliteServicesSupportedByCarriers = new HashMap<>();
     @NonNull private final Object mSupportedSatelliteServicesLock = new Object();
-    /** Key: PLMN, value: set of {@link android.telephony.NetworkRegistrationInfo.ServiceType} */
-    @NonNull private final Map<String, Set<Integer>> mSatelliteServicesSupportedByProviders;
+    @NonNull private final List<String> mSatellitePlmnListFromOverlayConfig;
     @NonNull private final CarrierConfigManager mCarrierConfigManager;
     @NonNull private final CarrierConfigManager.CarrierConfigChangeListener
             mCarrierConfigChangeListener;
     @NonNull private final Object mCarrierConfigArrayLock = new Object();
     @GuardedBy("mCarrierConfigArrayLock")
     @NonNull private final SparseArray<PersistableBundle> mCarrierConfigArray = new SparseArray<>();
-    @NonNull private final List<String> mSatellitePlmnList;
+    @GuardedBy("mIsSatelliteEnabledLock")
+    /** Key: Subscription ID, value: set of restriction reasons for satellite communication.*/
+    @NonNull private final Map<Integer, Set<Integer>> mSatelliteAttachRestrictionForCarrierArray =
+            new HashMap<>();
+    @GuardedBy("mIsSatelliteEnabledLock")
+    /** Key: Subscription ID, value: the actual satellite enabled state in the modem -
+     * {@code true} for enabled and {@code false} for disabled. */
+    @NonNull private final Map<Integer, Boolean> mIsSatelliteAttachEnabledForCarrierArrayPerSub =
+            new HashMap<>();
+    @NonNull private final FeatureFlags mFeatureFlags;
+    @NonNull private final Object mSatelliteConnectedLock = new Object();
+    /** Key: Subscription ID; Value: Last satellite connected time */
+    @GuardedBy("mSatelliteConnectedLock")
+    @NonNull private final SparseArray<Long> mLastSatelliteDisconnectedTimesMillis =
+            new SparseArray<>();
+    /**
+     * Key: Subscription ID; Value: {@code true} if satellite was just connected,
+     * {@code false} otherwise.
+     */
+    @GuardedBy("mSatelliteConnectedLock")
+    @NonNull private final SparseBooleanArray
+            mWasSatelliteConnectedViaCarrier = new SparseBooleanArray();
+
+    @GuardedBy("mSatelliteConnectedLock")
+    @NonNull private final SparseBooleanArray
+            mIsSatelliteConnectedViaCarrierHysteresisTimeExpired = new SparseBooleanArray();
+
+    /**
+     * This is used for testing only. When mEnforcedEmergencyCallToSatelliteHandoverType is valid,
+     * Telephony will ignore the IMS registration status and cellular availability, and always send
+     * the connection event EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer.
+     */
+    private int mEnforcedEmergencyCallToSatelliteHandoverType =
+            INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
+    private int mDelayInSendingEventDisplayEmergencyMessage = 0;
+    @NonNull private SharedPreferences mSharedPreferences = null;
+
+    /**
+     * Key : Subscription ID, Value: {@code true} if the EntitlementStatus is enabled,
+     * {@code false} otherwise.
+     */
+    @GuardedBy("mSupportedSatelliteServicesLock")
+    private SparseBooleanArray mSatelliteEntitlementStatusPerCarrier = new SparseBooleanArray();
+    /** Key Subscription ID, value : PLMN allowed list from entitlement. */
+    @GuardedBy("mSupportedSatelliteServicesLock")
+    private SparseArray<List<String>> mEntitlementPlmnListPerCarrier = new SparseArray<>();
+    /**
+     * Key : Subscription ID, Value : If there is an entitlementPlmnList, use it. Otherwise, use the
+     * carrierPlmnList. */
+    @GuardedBy("mSupportedSatelliteServicesLock")
+    private final SparseArray<List<String>> mMergedPlmnListPerCarrier = new SparseArray<>();
+    private static AtomicLong sNextSatelliteEnableRequestId = new AtomicLong(0);
+    private long mWaitTimeForSatelliteEnablingResponse;
+
+    /** Key used to read/write satellite system notification done in shared preferences. */
+    private static final String SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY =
+            "satellite_system_notification_done_key";
+    // The notification tag used when showing a notification. The combination of notification tag
+    // and notification id should be unique within the phone app.
+    private static final String NOTIFICATION_TAG = "SatelliteController";
+    private static final int NOTIFICATION_ID = 1;
+    private static final String NOTIFICATION_CHANNEL = "satelliteChannel";
+    private static final String NOTIFICATION_CHANNEL_ID = "satellite";
 
     /**
      * @return The singleton instance of SatelliteController.
@@ -240,12 +378,13 @@
     /**
      * Create the SatelliteController singleton instance.
      * @param context The Context to use to create the SatelliteController.
+     * @param featureFlags The feature flag.
      */
-    public static void make(@NonNull Context context) {
+    public static void make(@NonNull Context context, @NonNull FeatureFlags featureFlags) {
         if (sInstance == null) {
             HandlerThread satelliteThread = new HandlerThread(TAG);
             satelliteThread.start();
-            sInstance = new SatelliteController(context, satelliteThread.getLooper());
+            sInstance = new SatelliteController(context, satelliteThread.getLooper(), featureFlags);
         }
     }
 
@@ -255,14 +394,18 @@
      *
      * @param context The Context for the SatelliteController.
      * @param looper The looper for the handler. It does not run on main thread.
+     * @param featureFlags The feature flag.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public SatelliteController(@NonNull Context context, @NonNull Looper looper) {
+    public SatelliteController(
+            @NonNull Context context, @NonNull Looper looper, @NonNull FeatureFlags featureFlags) {
         super(looper);
 
         mContext = context;
+        mFeatureFlags = featureFlags;
         Phone phone = SatelliteServiceUtils.getPhone();
         mCi = phone.mCi;
+        mDSM = phone.getDeviceStateMonitor();
         // Create the SatelliteModemInterface singleton, which is used to manage connections
         // to the satellite service and HAL interface.
         mSatelliteModemInterface = SatelliteModemInterface.make(mContext, this);
@@ -275,33 +418,21 @@
         // should be called before making DatagramController
         mControllerMetricsStats = ControllerMetricsStats.make(mContext);
         mProvisionMetricsStats = ProvisionMetricsStats.getOrCreateInstance();
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
 
         // Create the DatagramController singleton,
         // which is used to send and receive satellite datagrams.
         mDatagramController = DatagramController.make(mContext, looper, mPointingAppController);
 
-        requestIsSatelliteSupported(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                new ResultReceiver(this) {
-                    @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        logd("requestIsSatelliteSupported: resultCode=" + resultCode);
-                    }
-                });
         mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
         mIsRadioOn = phone.isRadioOn();
         registerForSatelliteProvisionStateChanged();
         registerForPendingDatagramCount();
         registerForSatelliteModemStateChanged();
+        registerForServiceStateChanged();
         mContentResolver = mContext.getContentResolver();
         mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
 
-        try {
-            mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
-                    Context.MODE_PRIVATE);
-        } catch (Exception e) {
-            loge("Cannot get default shared preferences: " + e);
-        }
-
         initializeSatelliteModeRadios();
 
         ContentObserver satelliteModeRadiosContentObserver = new ContentObserver(this) {
@@ -316,15 +447,17 @@
                     false, satelliteModeRadiosContentObserver);
         }
 
-        mSatelliteServicesSupportedByProviders = readSupportedSatelliteServicesFromOverlayConfig();
-        mSatellitePlmnList =
-                mSatelliteServicesSupportedByProviders.keySet().stream().toList();
+        mSatellitePlmnListFromOverlayConfig = readSatellitePlmnsFromOverlayConfig();
         updateSupportedSatelliteServicesForActiveSubscriptions();
         mCarrierConfigChangeListener =
                 (slotIndex, subId, carrierId, specificCarrierId) ->
                         handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
         mCarrierConfigManager.registerCarrierConfigChangeListener(
                         new HandlerExecutor(new Handler(looper)), mCarrierConfigChangeListener);
+        mDSM.registerForSignalStrengthReportDecision(this, CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING,
+                null);
+        loadSatelliteSharedPreferences();
+        mWaitTimeForSatelliteEnablingResponse = getWaitForSatelliteEnablingResponseTimeoutMillis();
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -532,12 +665,30 @@
         public boolean enableSatellite;
         public boolean enableDemoMode;
         @NonNull public Consumer<Integer> callback;
+        public long requestId;
 
         RequestSatelliteEnabledArgument(boolean enableSatellite, boolean enableDemoMode,
                 Consumer<Integer> callback) {
             this.enableSatellite = enableSatellite;
             this.enableDemoMode = enableDemoMode;
             this.callback = callback;
+            this.requestId = sNextSatelliteEnableRequestId.getAndUpdate(
+                    n -> ((n + 1) % Long.MAX_VALUE));
+        }
+    }
+
+    private static final class RequestHandleSatelliteAttachRestrictionForCarrierArgument {
+        public int subId;
+        @SatelliteManager.SatelliteCommunicationRestrictionReason
+        public int reason;
+        @NonNull public Consumer<Integer> callback;
+
+        RequestHandleSatelliteAttachRestrictionForCarrierArgument(int subId,
+                @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
+                Consumer<Integer> callback) {
+            this.subId = subId;
+            this.reason = reason;
+            this.callback = callback;
         }
     }
 
@@ -583,8 +734,7 @@
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted =
                         obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, request);
-                mPointingAppController.startSatelliteTransmissionUpdates(onCompleted,
-                        request.phone);
+                mPointingAppController.startSatelliteTransmissionUpdates(onCompleted);
                 break;
             }
 
@@ -597,7 +747,7 @@
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted =
                         obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, request);
-                mPointingAppController.stopSatelliteTransmissionUpdates(onCompleted, request.phone);
+                mPointingAppController.stopSatelliteTransmissionUpdates(onCompleted);
                 break;
             }
 
@@ -616,7 +766,7 @@
                         (ProvisionSatelliteServiceArgument) request.argument;
                 if (mSatelliteProvisionCallbacks.containsKey(argument.subId)) {
                     argument.callback.accept(
-                            SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS);
+                            SatelliteManager.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS);
                     notifyRequester(request);
                     break;
                 }
@@ -624,24 +774,8 @@
                 onCompleted = obtainMessage(EVENT_PROVISION_SATELLITE_SERVICE_DONE, request);
                 // Log the current time for provision triggered
                 mProvisionMetricsStats.setProvisioningStartTime();
-                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-                    mSatelliteModemInterface.provisionSatelliteService(argument.token,
-                            argument.provisionData, onCompleted);
-                    break;
-                }
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.provisionSatelliteService(onCompleted, argument.token);
-                } else {
-                    loge("provisionSatelliteService: No phone object");
-                    argument.callback.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                    notifyRequester(request);
-                    mProvisionMetricsStats
-                            .setResultCode(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)
-                            .reportProvisionMetrics();
-                    mControllerMetricsStats.reportProvisionCount(
-                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                }
+                mSatelliteModemInterface.provisionSatelliteService(argument.token,
+                        argument.provisionData, onCompleted);
                 break;
             }
 
@@ -664,26 +798,7 @@
                 if (argument.callback != null) {
                     mProvisionMetricsStats.setProvisioningStartTime();
                 }
-                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-                    mSatelliteModemInterface
-                            .deprovisionSatelliteService(argument.token, onCompleted);
-                    break;
-                }
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.deprovisionSatelliteService(onCompleted, argument.token);
-                } else {
-                    loge("deprovisionSatelliteService: No phone object");
-                    if (argument.callback != null) {
-                        argument.callback.accept(
-                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                        mProvisionMetricsStats
-                                .setResultCode(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)
-                                .reportProvisionMetrics();
-                        mControllerMetricsStats.reportDeprovisionCount(
-                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-                    }
-                }
+                mSatelliteModemInterface.deprovisionSatelliteService(argument.token, onCompleted);
                 break;
             }
 
@@ -711,21 +826,32 @@
                 int error =  SatelliteServiceUtils.getSatelliteError(ar, "setSatelliteEnabled");
                 logd("EVENT_SET_SATELLITE_ENABLED_DONE = " + error);
 
-                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                /*
+                 * The timer to wait for EVENT_SET_SATELLITE_ENABLED_DONE might have expired and
+                 * thus the request resources might have been cleaned up.
+                 */
+                if (!shouldProcessEventSetSatelliteEnabledDone(argument)) {
+                    logw("The request ID=" + argument.requestId + ", enableSatellite="
+                            + argument.enableSatellite + " was already processed");
+                    return;
+                }
+                stopWaitForSatelliteEnablingResponseTimer(argument);
+
+                if (error == SATELLITE_RESULT_SUCCESS) {
                     if (argument.enableSatellite) {
                         synchronized (mSatelliteEnabledRequestLock) {
                             mWaitingForRadioDisabled = true;
                         }
                         setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_TRUE);
-
-                        /**
-                         * TODO for NTN-based satellites: Check if satellite is acquired.
-                         */
-                        if (mNeedsSatellitePointing) {
-                            mPointingAppController.startPointingUI(false);
-                        }
                         evaluateToSendSatelliteEnabledSuccess();
                     } else {
+                        /**
+                         * Unregister Importance Listener for Pointing UI
+                         * when Satellite is disabled
+                         */
+                        if (mNeedsSatellitePointing) {
+                            mPointingAppController.removeListenerForPointingUI();
+                        }
                         synchronized (mSatelliteEnabledRequestLock) {
                             if (mSatelliteEnabledRequest != null &&
                                     mSatelliteEnabledRequest.enableSatellite == true &&
@@ -733,14 +859,15 @@
                                 // Previous mSatelliteEnabledRequest is successful but waiting for
                                 // all radios to be turned off.
                                 mSatelliteEnabledRequest.callback.accept(
-                                        SatelliteManager.SATELLITE_ERROR_NONE);
+                                        SATELLITE_RESULT_SUCCESS);
                             }
                         }
 
                         synchronized (mIsSatelliteEnabledLock) {
                             if (!mWaitingForSatelliteModemOff) {
                                 moveSatelliteToOffStateAndCleanUpResources(
-                                        SatelliteManager.SATELLITE_ERROR_NONE, argument.callback);
+                                        SATELLITE_RESULT_SUCCESS,
+                                        argument.callback);
                             } else {
                                 logd("Wait for satellite modem off before updating satellite"
                                         + " modem state");
@@ -756,7 +883,7 @@
                             // Previous mSatelliteEnabledRequest is successful but waiting for
                             // all radios to be turned off.
                             mSatelliteEnabledRequest.callback.accept(
-                                    SatelliteManager.SATELLITE_ERROR_NONE);
+                                    SATELLITE_RESULT_SUCCESS);
                         }
                     }
                     resetSatelliteEnabledRequest();
@@ -766,7 +893,7 @@
                 }
 
                 if (argument.enableSatellite) {
-                    if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    if (error == SATELLITE_RESULT_SUCCESS) {
                         mControllerMetricsStats.onSatelliteEnabled();
                         mControllerMetricsStats.reportServiceEnablementSuccessCount();
                     } else {
@@ -774,7 +901,7 @@
                     }
                     SessionMetricsStats.getInstance()
                             .setInitializationResult(error)
-                            .setRadioTechnology(SatelliteManager.NT_RADIO_TECHNOLOGY_PROPRIETARY)
+                            .setRadioTechnology(getSupportedNtnRadioTechnology())
                             .reportSessionMetrics();
                 } else {
                     mControllerMetricsStats.onSatelliteDisabled();
@@ -785,21 +912,15 @@
                 break;
             }
 
+            case EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT:
+                handleEventWaitForSatelliteEnablingResponseTimedOut(
+                        (RequestSatelliteEnabledArgument) msg.obj);
+                break;
+
             case CMD_IS_SATELLITE_ENABLED: {
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted = obtainMessage(EVENT_IS_SATELLITE_ENABLED_DONE, request);
-                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-                    mSatelliteModemInterface.requestIsSatelliteEnabled(onCompleted);
-                    break;
-                }
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.isSatellitePowerOn(onCompleted);
-                } else {
-                    loge("isSatelliteEnabled: No phone object");
-                    ((ResultReceiver) request.argument).send(
-                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-                }
+                mSatelliteModemInterface.requestIsSatelliteEnabled(onCompleted);
                 break;
             }
 
@@ -809,17 +930,17 @@
                 int error =  SatelliteServiceUtils.getSatelliteError(ar,
                         "isSatelliteEnabled");
                 Bundle bundle = new Bundle();
-                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                if (error == SATELLITE_RESULT_SUCCESS) {
                     if (ar.result == null) {
                         loge("isSatelliteEnabled: result is null");
-                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                        error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     } else {
                         boolean enabled = ((int[]) ar.result)[0] == 1;
                         if (DBG) logd("isSatelliteEnabled: " + enabled);
                         bundle.putBoolean(SatelliteManager.KEY_SATELLITE_ENABLED, enabled);
                         updateSatelliteEnabledState(enabled, "EVENT_IS_SATELLITE_ENABLED_DONE");
                     }
-                } else if (error == SatelliteManager.SATELLITE_REQUEST_NOT_SUPPORTED) {
+                } else if (error == SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED) {
                     updateSatelliteSupportedStateWhenSatelliteServiceConnected(false);
                 }
                 ((ResultReceiver) request.argument).send(error, bundle);
@@ -829,19 +950,7 @@
             case CMD_IS_SATELLITE_SUPPORTED: {
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted = obtainMessage(EVENT_IS_SATELLITE_SUPPORTED_DONE, request);
-
-                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-                    mSatelliteModemInterface.requestIsSatelliteSupported(onCompleted);
-                    break;
-                }
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.isSatelliteSupported(onCompleted);
-                } else {
-                    loge("isSatelliteSupported: No phone object");
-                    ((ResultReceiver) request.argument).send(
-                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-                }
+                mSatelliteModemInterface.requestIsSatelliteSupported(onCompleted);
                 break;
             }
 
@@ -850,13 +959,13 @@
                 request = (SatelliteControllerHandlerRequest) ar.userObj;
                 int error =  SatelliteServiceUtils.getSatelliteError(ar, "isSatelliteSupported");
                 Bundle bundle = new Bundle();
-                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                if (error == SATELLITE_RESULT_SUCCESS) {
                     if (ar.result == null) {
                         loge("isSatelliteSupported: result is null");
-                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                        error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     } else {
                         boolean supported = (boolean) ar.result;
-                        if (DBG) logd("isSatelliteSupported: " + supported);
+                        logd("isSatelliteSupported: " + supported);
                         bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, supported);
                         updateSatelliteSupportedStateWhenSatelliteServiceConnected(supported);
                     }
@@ -868,18 +977,7 @@
             case CMD_GET_SATELLITE_CAPABILITIES: {
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted = obtainMessage(EVENT_GET_SATELLITE_CAPABILITIES_DONE, request);
-                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-                    mSatelliteModemInterface.requestSatelliteCapabilities(onCompleted);
-                    break;
-                }
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.getSatelliteCapabilities(onCompleted);
-                } else {
-                    loge("getSatelliteCapabilities: No phone object");
-                    ((ResultReceiver) request.argument).send(
-                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-                }
+                mSatelliteModemInterface.requestSatelliteCapabilities(onCompleted);
                 break;
             }
 
@@ -889,10 +987,10 @@
                 int error =  SatelliteServiceUtils.getSatelliteError(ar,
                         "getSatelliteCapabilities");
                 Bundle bundle = new Bundle();
-                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                if (error == SATELLITE_RESULT_SUCCESS) {
                     if (ar.result == null) {
                         loge("getSatelliteCapabilities: result is null");
-                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                        error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     } else {
                         SatelliteCapabilities capabilities = (SatelliteCapabilities) ar.result;
                         synchronized (mNeedsSatellitePointingLock) {
@@ -914,20 +1012,8 @@
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted =
                         obtainMessage(EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE, request);
-                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-                    mSatelliteModemInterface
-                            .requestIsSatelliteCommunicationAllowedForCurrentLocation(
-                                    onCompleted);
-                    break;
-                }
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.isSatelliteCommunicationAllowedForCurrentLocation(onCompleted);
-                } else {
-                    loge("isSatelliteCommunicationAllowedForCurrentLocation: No phone object");
-                    ((ResultReceiver) request.argument).send(
-                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-                }
+                mSatelliteModemInterface
+                        .requestIsSatelliteCommunicationAllowedForCurrentLocation(onCompleted);
                 break;
             }
 
@@ -937,10 +1023,10 @@
                 int error =  SatelliteServiceUtils.getSatelliteError(ar,
                         "isSatelliteCommunicationAllowedForCurrentLocation");
                 Bundle bundle = new Bundle();
-                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                if (error == SATELLITE_RESULT_SUCCESS) {
                     if (ar.result == null) {
                         loge("isSatelliteCommunicationAllowedForCurrentLocation: result is null");
-                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                        error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     } else {
                         boolean communicationAllowed = (boolean) ar.result;
                         if (DBG) {
@@ -959,19 +1045,7 @@
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted = obtainMessage(EVENT_GET_TIME_SATELLITE_NEXT_VISIBLE_DONE,
                         request);
-                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-                    mSatelliteModemInterface
-                            .requestTimeForNextSatelliteVisibility(onCompleted);
-                    break;
-                }
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.requestTimeForNextSatelliteVisibility(onCompleted);
-                } else {
-                    loge("requestTimeForNextSatelliteVisibility: No phone object");
-                    ((ResultReceiver) request.argument).send(
-                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-                }
+                mSatelliteModemInterface.requestTimeForNextSatelliteVisibility(onCompleted);
                 break;
             }
 
@@ -981,10 +1055,10 @@
                 int error = SatelliteServiceUtils.getSatelliteError(ar,
                         "requestTimeForNextSatelliteVisibility");
                 Bundle bundle = new Bundle();
-                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                if (error == SATELLITE_RESULT_SUCCESS) {
                     if (ar.result == null) {
                         loge("requestTimeForNextSatelliteVisibility: result is null");
-                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                        error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     } else {
                         int nextVisibilityDuration = ((int[]) ar.result)[0];
                         if (DBG) {
@@ -1000,48 +1074,25 @@
             }
 
             case EVENT_RADIO_STATE_CHANGED: {
-                if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF
-                        || mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
-                    mIsRadioOn = false;
-                    logd("Radio State Changed to " + mCi.getRadioState());
-                    if (isSatelliteEnabled()) {
-                        IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
-                            @Override
-                            public void accept(int result) {
-                                logd("RequestSatelliteEnabled: result=" + result);
-                            }
-                        };
-                        Phone phone = SatelliteServiceUtils.getPhone();
-                        Consumer<Integer> result = FunctionalUtils
-                                .ignoreRemoteException(errorCallback::accept);
-                        RequestSatelliteEnabledArgument message =
-                                new RequestSatelliteEnabledArgument(false, false, result);
-                        request = new SatelliteControllerHandlerRequest(message, phone);
-                        handleSatelliteEnabled(request);
-                    } else {
-                        logd("EVENT_RADIO_STATE_CHANGED: Satellite modem is currently disabled."
-                                + " Ignored the event");
-                    }
-                } else {
+                if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
                     mIsRadioOn = true;
-                    if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                }
+                if (mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
+                    if (mSatelliteModemInterface.isSatelliteServiceConnected()) {
                         synchronized (mIsSatelliteSupportedLock) {
-                            if (mIsSatelliteSupported == null) {
+                            if (mIsSatelliteSupported == null || !mIsSatelliteSupported) {
                                 ResultReceiver receiver = new ResultReceiver(this) {
                                     @Override
                                     protected void onReceiveResult(
                                             int resultCode, Bundle resultData) {
-                                        logd("requestIsSatelliteSupported: resultCode="
-                                                + resultCode);
+                                        logd("onRadioStateChanged.requestIsSatelliteSupported: "
+                                                + "resultCode=" + resultCode
+                                                + ", resultData=" + resultData);
                                     }
                                 };
-                                requestIsSatelliteSupported(
-                                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, receiver);
+                                sendRequestAsync(CMD_IS_SATELLITE_SUPPORTED, receiver, null);
                             }
                         }
-                    } else {
-                        logd("EVENT_RADIO_STATE_CHANGED: Satellite vendor service is supported."
-                                + " Ignored the event");
                     }
                 }
                 break;
@@ -1050,41 +1101,12 @@
             case CMD_IS_SATELLITE_PROVISIONED: {
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted = obtainMessage(EVENT_IS_SATELLITE_PROVISIONED_DONE, request);
-                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-                    mSatelliteModemInterface.requestIsSatelliteProvisioned(onCompleted);
-                    break;
-                }
-                Phone phone = request.phone;
-                if (phone != null) {
-                    phone.isSatelliteProvisioned(onCompleted);
-                } else {
-                    loge("isSatelliteProvisioned: No phone object");
-                    ((ResultReceiver) request.argument).send(
-                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-                }
+                mSatelliteModemInterface.requestIsSatelliteProvisioned(onCompleted);
                 break;
             }
 
             case EVENT_IS_SATELLITE_PROVISIONED_DONE: {
-                ar = (AsyncResult) msg.obj;
-                request = (SatelliteControllerHandlerRequest) ar.userObj;
-                int error =  SatelliteServiceUtils.getSatelliteError(ar,
-                        "isSatelliteProvisioned");
-                Bundle bundle = new Bundle();
-                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
-                    if (ar.result == null) {
-                        loge("isSatelliteProvisioned: result is null");
-                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
-                    } else {
-                        boolean provisioned = ((int[]) ar.result)[0] == 1;
-                        if (DBG) logd("isSatelliteProvisioned: " + provisioned);
-                        bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED, provisioned);
-                        synchronized (mIsSatelliteProvisionedLock) {
-                            mIsSatelliteProvisioned = provisioned;
-                        }
-                    }
-                }
-                ((ResultReceiver) request.argument).send(error, bundle);
+                handleIsSatelliteProvisionedDoneEvent((AsyncResult) msg.obj);
                 break;
             }
 
@@ -1105,7 +1127,7 @@
                         logd("pollPendingSatelliteDatagram result: " + result);
                     }
                 };
-                pollPendingSatelliteDatagrams(
+                pollPendingDatagrams(
                         SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, internalCallback);
                 break;
 
@@ -1118,6 +1140,153 @@
                 }
                 break;
 
+            case EVENT_SET_SATELLITE_PLMN_INFO_DONE:
+                handleSetSatellitePlmnInfoDoneEvent(msg);
+                break;
+
+            case CMD_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE: {
+                logd("CMD_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE");
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                handleRequestSatelliteAttachRestrictionForCarrierCmd(request);
+                break;
+            }
+
+            case EVENT_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                RequestHandleSatelliteAttachRestrictionForCarrierArgument argument =
+                        (RequestHandleSatelliteAttachRestrictionForCarrierArgument)
+                                request.argument;
+                int subId = argument.subId;
+                int error =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "requestSetSatelliteEnabledForCarrier");
+
+                synchronized (mIsSatelliteEnabledLock) {
+                    if (error == SATELLITE_RESULT_SUCCESS) {
+                        boolean enableSatellite = mSatelliteAttachRestrictionForCarrierArray
+                                .getOrDefault(argument.subId, Collections.emptySet()).isEmpty();
+                        mIsSatelliteAttachEnabledForCarrierArrayPerSub.put(subId, enableSatellite);
+                    } else {
+                        mIsSatelliteAttachEnabledForCarrierArrayPerSub.remove(subId);
+                    }
+                }
+
+                argument.callback.accept(error);
+                break;
+            }
+
+            case CMD_REQUEST_NTN_SIGNAL_STRENGTH: {
+                logd("CMD_REQUEST_NTN_SIGNAL_STRENGTH");
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted = obtainMessage(EVENT_REQUEST_NTN_SIGNAL_STRENGTH_DONE, request);
+                mSatelliteModemInterface.requestNtnSignalStrength(onCompleted);
+                break;
+            }
+
+            case EVENT_REQUEST_NTN_SIGNAL_STRENGTH_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                ResultReceiver result = (ResultReceiver) request.argument;
+                int errorCode =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "requestNtnSignalStrength");
+                if (errorCode == SATELLITE_RESULT_SUCCESS) {
+                    NtnSignalStrength ntnSignalStrength = (NtnSignalStrength) ar.result;
+                    if (ntnSignalStrength != null) {
+                        synchronized (mNtnSignalsStrengthLock) {
+                            mNtnSignalStrength = ntnSignalStrength;
+                        }
+                        Bundle bundle = new Bundle();
+                        bundle.putParcelable(KEY_NTN_SIGNAL_STRENGTH, ntnSignalStrength);
+                        result.send(SATELLITE_RESULT_SUCCESS, bundle);
+                    } else {
+                        synchronized (mNtnSignalsStrengthLock) {
+                            if (mNtnSignalStrength.getLevel() != NTN_SIGNAL_STRENGTH_NONE) {
+                                mNtnSignalStrength = new NtnSignalStrength(
+                                        NTN_SIGNAL_STRENGTH_NONE);
+                            }
+                        }
+                        loge("EVENT_REQUEST_NTN_SIGNAL_STRENGTH_DONE: ntnSignalStrength is null");
+                        result.send(SatelliteManager.SATELLITE_RESULT_REQUEST_FAILED, null);
+                    }
+                } else {
+                    synchronized (mNtnSignalsStrengthLock) {
+                        if (mNtnSignalStrength.getLevel() != NTN_SIGNAL_STRENGTH_NONE) {
+                            mNtnSignalStrength = new NtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE);
+                        }
+                    }
+                    result.send(errorCode, null);
+                }
+                break;
+            }
+
+            case EVENT_NTN_SIGNAL_STRENGTH_CHANGED: {
+                ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_NTN_SIGNAL_STRENGTH_CHANGED: result is null");
+                } else {
+                    handleEventNtnSignalStrengthChanged((NtnSignalStrength) ar.result);
+                }
+                break;
+            }
+
+            case CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING: {
+                ar = (AsyncResult) msg.obj;
+                boolean shouldReport = (boolean) ar.result;
+                if (DBG) {
+                    logd("CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING: shouldReport=" + shouldReport);
+                }
+                request = new SatelliteControllerHandlerRequest(shouldReport,
+                        SatelliteServiceUtils.getPhone());
+                if (SATELLITE_RESULT_SUCCESS != evaluateOemSatelliteRequestAllowed(true)) {
+                    return;
+                }
+                if (mShouldReportNtnSignalStrength.get() == shouldReport) {
+                    if (DBG) {
+                        logd("CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING : modem state matches the "
+                                + "expected state, return.");
+                    }
+                    return;
+                }
+                onCompleted = obtainMessage(EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE,
+                        request);
+                if (shouldReport) {
+                    mSatelliteModemInterface.startSendingNtnSignalStrength(onCompleted);
+                } else {
+                    mSatelliteModemInterface.stopSendingNtnSignalStrength(onCompleted);
+                }
+                break;
+            }
+
+            case EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                boolean shouldReport = (boolean) request.argument;
+                int errorCode =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE");
+                if (errorCode == SATELLITE_RESULT_SUCCESS) {
+                    mShouldReportNtnSignalStrength.set(shouldReport);
+                } else {
+                    loge(((boolean) request.argument ? "startSendingNtnSignalStrength"
+                            : "stopSendingNtnSignalStrength") + "returns " + errorCode);
+                }
+                break;
+            }
+
+            case EVENT_SERVICE_STATE_CHANGED: {
+                handleEventServiceStateChanged();
+                break;
+            }
+
+            case EVENT_SATELLITE_CAPABILITIES_CHANGED: {
+                ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_SATELLITE_CAPABILITIES_CHANGED: result is null");
+                } else {
+                    handleEventSatelliteCapabilitiesChanged((SatelliteCapabilities) ar.result);
+                }
+                break;
+            }
+
             default:
                 Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " +
                         msg.what);
@@ -1146,33 +1315,18 @@
             @NonNull IIntegerConsumer callback) {
         logd("requestSatelliteEnabled subId: " + subId + " enableSatellite: " + enableSatellite
                 + " enableDemoMode: " + enableDemoMode);
-
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
-
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
-            return;
-        }
-
-        Boolean satelliteProvisioned = isSatelliteProvisioned();
-        if (satelliteProvisioned == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteProvisioned) {
-            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            sendErrorAndReportSessionMetrics(error, result);
             return;
         }
 
         if (enableSatellite) {
             if (!mIsRadioOn) {
                 loge("Radio is not on, can not enable satellite");
-                result.accept(SatelliteManager.SATELLITE_INVALID_MODEM_STATE);
+                sendErrorAndReportSessionMetrics(
+                        SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE, result);
                 return;
             }
         } else {
@@ -1186,12 +1340,14 @@
                     if (enableDemoMode != mIsDemoModeEnabled) {
                         loge("Received invalid demo mode while satellite session is enabled"
                                 + " enableDemoMode = " + enableDemoMode);
-                        result.accept(SatelliteManager.SATELLITE_INVALID_ARGUMENTS);
+                        sendErrorAndReportSessionMetrics(
+                                SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS, result);
                         return;
                     } else {
                         logd("Enable request matches with current state"
                                 + " enableSatellite = " + enableSatellite);
-                        result.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+                        sendErrorAndReportSessionMetrics(
+                                SatelliteManager.SATELLITE_RESULT_SUCCESS, result);
                         return;
                     }
                 }
@@ -1206,7 +1362,8 @@
          * 2. If there is a ongoing request, then:
          *      1. ongoing request = enable, current request = enable: return IN_PROGRESS error
          *      2. ongoing request = disable, current request = disable: return IN_PROGRESS error
-         *      3. ongoing request = disable, current request = enable: return SATELLITE_ERROR error
+         *      3. ongoing request = disable, current request = enable: return
+         *      SATELLITE_RESULT_ERROR error
          *      4. ongoing request = enable, current request = disable: send request to modem
          */
         synchronized (mSatelliteEnabledRequestLock) {
@@ -1215,18 +1372,20 @@
             } else if (mSatelliteEnabledRequest.enableSatellite == request.enableSatellite) {
                 logd("requestSatelliteEnabled enableSatellite: " + enableSatellite
                         + " is already in progress.");
-                result.accept(SatelliteManager.SATELLITE_REQUEST_IN_PROGRESS);
+                sendErrorAndReportSessionMetrics(
+                        SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS, result);
                 return;
             } else if (mSatelliteEnabledRequest.enableSatellite == false
                     && request.enableSatellite == true) {
                 logd("requestSatelliteEnabled enableSatellite: " + enableSatellite + " cannot be "
                         + "processed. Disable satellite is already in progress.");
-                result.accept(SatelliteManager.SATELLITE_ERROR);
+                sendErrorAndReportSessionMetrics(
+                        SatelliteManager.SATELLITE_RESULT_ERROR, result);
                 return;
             }
         }
 
-        sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, SatelliteServiceUtils.getPhone());
+        sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
     }
 
     /**
@@ -1237,13 +1396,9 @@
      *               if the request is successful or an error code if the request failed.
      */
     public void requestIsSatelliteEnabled(int subId, @NonNull ResultReceiver result) {
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+        int error = evaluateOemSatelliteRequestAllowed(false);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.send(error, null);
             return;
         }
 
@@ -1252,12 +1407,12 @@
                 /* We have already successfully queried the satellite modem. */
                 Bundle bundle = new Bundle();
                 bundle.putBoolean(SatelliteManager.KEY_SATELLITE_ENABLED, mIsSatelliteEnabled);
-                result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+                result.send(SATELLITE_RESULT_SUCCESS, bundle);
                 return;
             }
         }
 
-        sendRequestAsync(CMD_IS_SATELLITE_ENABLED, result, SatelliteServiceUtils.getPhone());
+        sendRequestAsync(CMD_IS_SATELLITE_ENABLED, result, null);
     }
 
     /**
@@ -1267,6 +1422,10 @@
      * @return {@code true} if the satellite modem is enabled and {@code false} otherwise.
      */
     public boolean isSatelliteEnabled() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("isSatelliteEnabled: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
         if (mIsSatelliteEnabled == null) return false;
         return mIsSatelliteEnabled;
     }
@@ -1280,29 +1439,15 @@
      *               if the request is successful or an error code if the request failed.
      */
     public void requestIsDemoModeEnabled(int subId, @NonNull ResultReceiver result) {
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
-            return;
-        }
-
-        Boolean satelliteProvisioned = isSatelliteProvisioned();
-        if (satelliteProvisioned == null) {
-            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-            return;
-        }
-        if (!satelliteProvisioned) {
-            result.send(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED, null);
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.send(error, null);
             return;
         }
 
         final Bundle bundle = new Bundle();
         bundle.putBoolean(SatelliteManager.KEY_DEMO_MODE_ENABLED, mIsDemoModeEnabled);
-        result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+        result.send(SATELLITE_RESULT_SUCCESS, bundle);
     }
 
     /**
@@ -1311,6 +1456,10 @@
      * @return {@code true} if the satellite demo mode is enabled and {@code false} otherwise.
      */
     public boolean isDemoModeEnabled() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("isDemoModeEnabled: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
         return mIsDemoModeEnabled;
     }
 
@@ -1322,17 +1471,22 @@
      *               the device if the request is successful or an error code if the request failed.
      */
     public void requestIsSatelliteSupported(int subId, @NonNull ResultReceiver result) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("requestIsSatelliteSupported: oemEnabledSatelliteFlag is disabled");
+            result.send(SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED, null);
+            return;
+        }
         synchronized (mIsSatelliteSupportedLock) {
             if (mIsSatelliteSupported != null) {
                 /* We have already successfully queried the satellite modem. */
                 Bundle bundle = new Bundle();
                 bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, mIsSatelliteSupported);
-                result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+                result.send(SATELLITE_RESULT_SUCCESS, bundle);
                 return;
             }
         }
 
-        sendRequestAsync(CMD_IS_SATELLITE_SUPPORTED, result, SatelliteServiceUtils.getPhone());
+        sendRequestAsync(CMD_IS_SATELLITE_SUPPORTED, result, null);
     }
 
     /**
@@ -1343,13 +1497,9 @@
      *               if the request is successful or an error code if the request failed.
      */
     public void requestSatelliteCapabilities(int subId, @NonNull ResultReceiver result) {
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+        int error = evaluateOemSatelliteRequestAllowed(false);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.send(error, null);
             return;
         }
 
@@ -1358,12 +1508,12 @@
                 Bundle bundle = new Bundle();
                 bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES,
                         mSatelliteCapabilities);
-                result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+                result.send(SATELLITE_RESULT_SUCCESS, bundle);
                 return;
             }
         }
 
-        sendRequestAsync(CMD_GET_SATELLITE_CAPABILITIES, result, SatelliteServiceUtils.getPhone());
+        sendRequestAsync(CMD_GET_SATELLITE_CAPABILITIES, result, null);
     }
 
     /**
@@ -1379,31 +1529,16 @@
             @NonNull IIntegerConsumer errorCallback,
             @NonNull ISatelliteTransmissionUpdateCallback callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(errorCallback::accept);
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.accept(error);
             return;
         }
 
-        Boolean satelliteProvisioned = isSatelliteProvisioned();
-        if (satelliteProvisioned == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteProvisioned) {
-            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
-            return;
-        }
-
-        Phone phone = SatelliteServiceUtils.getPhone();
         final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
-        mPointingAppController.registerForSatelliteTransmissionUpdates(validSubId, callback, phone);
+        mPointingAppController.registerForSatelliteTransmissionUpdates(validSubId, callback);
         sendRequestAsync(CMD_START_SATELLITE_TRANSMISSION_UPDATES,
-                new SatelliteTransmissionUpdateArgument(result, callback, validSubId), phone);
+                new SatelliteTransmissionUpdateArgument(result, callback, validSubId), null);
     }
 
     /**
@@ -1418,35 +1553,20 @@
     public void stopSatelliteTransmissionUpdates(int subId, @NonNull IIntegerConsumer errorCallback,
             @NonNull ISatelliteTransmissionUpdateCallback callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(errorCallback::accept);
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.accept(error);
             return;
         }
 
-        Boolean satelliteProvisioned = isSatelliteProvisioned();
-        if (satelliteProvisioned == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteProvisioned) {
-            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
-            return;
-        }
-
-        Phone phone = SatelliteServiceUtils.getPhone();
         final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(
-                validSubId, result, callback, phone);
+                validSubId, result, callback);
 
         // Even if handler is null - which means there are no listeners, the modem command to stop
         // satellite transmission updates might have failed. The callers might want to retry
         // sending the command. Thus, we always need to send this command to the modem.
-        sendRequestAsync(CMD_STOP_SATELLITE_TRANSMISSION_UPDATES, result, phone);
+        sendRequestAsync(CMD_STOP_SATELLITE_TRANSMISSION_UPDATES, result, null);
     }
 
     /**
@@ -1466,38 +1586,33 @@
             @NonNull String token, @NonNull byte[] provisionData,
             @NonNull IIntegerConsumer callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return null;
-        }
-        if (!satelliteSupported) {
-            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+        int error = evaluateOemSatelliteRequestAllowed(false);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.accept(error);
             return null;
         }
 
         final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
         if (mSatelliteProvisionCallbacks.containsKey(validSubId)) {
-            result.accept(SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS);
+            result.accept(SatelliteManager.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS);
             return null;
         }
 
-        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        Boolean satelliteProvisioned = isSatelliteViaOemProvisioned();
         if (satelliteProvisioned != null && satelliteProvisioned) {
-            result.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+            result.accept(SATELLITE_RESULT_SUCCESS);
             return null;
         }
 
-        Phone phone = SatelliteServiceUtils.getPhone();
         sendRequestAsync(CMD_PROVISION_SATELLITE_SERVICE,
                 new ProvisionSatelliteServiceArgument(token, provisionData, result, validSubId),
-                phone);
+                null);
 
         ICancellationSignal cancelTransport = CancellationSignal.createTransport();
         CancellationSignal.fromTransport(cancelTransport).setOnCancelListener(() -> {
             sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
                     new ProvisionSatelliteServiceArgument(token, provisionData, null,
-                            validSubId), phone);
+                            validSubId), null);
             mProvisionMetricsStats.setIsCanceled(true);
         });
         return cancelTransport;
@@ -1517,31 +1632,26 @@
     public void deprovisionSatelliteService(int subId,
             @NonNull String token, @NonNull IIntegerConsumer callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+        int error = evaluateOemSatelliteRequestAllowed(false);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.accept(error);
             return;
         }
 
-        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        Boolean satelliteProvisioned = isSatelliteViaOemProvisioned();
         if (satelliteProvisioned == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            result.accept(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
             return;
         }
         if (!satelliteProvisioned) {
-            result.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+            result.accept(SATELLITE_RESULT_SUCCESS);
             return;
         }
 
-        Phone phone = SatelliteServiceUtils.getPhone();
         final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
         sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
                 new ProvisionSatelliteServiceArgument(token, null, result, validSubId),
-                phone);
+                null);
     }
 
     /**
@@ -1550,20 +1660,17 @@
      * @param subId The subId of the subscription to register for provision state changed.
      * @param callback The callback to handle the satellite provision state changed event.
      *
-     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
-    @SatelliteManager.SatelliteError public int registerForSatelliteProvisionStateChanged(int subId,
-            @NonNull ISatelliteProvisionStateCallback callback) {
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            return SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
-        }
-        if (!satelliteSupported) {
-            return SatelliteManager.SATELLITE_NOT_SUPPORTED;
+    @SatelliteManager.SatelliteResult public int registerForSatelliteProvisionStateChanged(
+            int subId, @NonNull ISatelliteProvisionStateCallback callback) {
+        int error = evaluateOemSatelliteRequestAllowed(false);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            return error;
         }
 
         mSatelliteProvisionStateChangedListeners.put(callback.asBinder(), callback);
-        return SatelliteManager.SATELLITE_ERROR_NONE;
+        return SATELLITE_RESULT_SUCCESS;
     }
 
     /**
@@ -1576,6 +1683,11 @@
      */
     public void unregisterForSatelliteProvisionStateChanged(
             int subId, @NonNull ISatelliteProvisionStateCallback callback) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("unregisterForSatelliteProvisionStateChanged: "
+                    + "oemEnabledSatelliteFlag is disabled");
+            return;
+        }
         mSatelliteProvisionStateChangedListeners.remove(callback.asBinder());
     }
 
@@ -1588,27 +1700,23 @@
      *               request failed.
      */
     public void requestIsSatelliteProvisioned(int subId, @NonNull ResultReceiver result) {
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+        int error = evaluateOemSatelliteRequestAllowed(false);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.send(error, null);
             return;
         }
 
-        synchronized (mIsSatelliteProvisionedLock) {
-            if (mIsSatelliteProvisioned != null) {
+        synchronized (mSatelliteViaOemProvisionLock) {
+            if (mIsSatelliteViaOemProvisioned != null) {
                 Bundle bundle = new Bundle();
                 bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED,
-                        mIsSatelliteProvisioned);
-                result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+                        mIsSatelliteViaOemProvisioned);
+                result.send(SATELLITE_RESULT_SUCCESS, bundle);
                 return;
             }
         }
 
-        sendRequestAsync(CMD_IS_SATELLITE_PROVISIONED, result, SatelliteServiceUtils.getPhone());
+        sendRequestAsync(CMD_IS_SATELLITE_PROVISIONED, result, null);
     }
 
     /**
@@ -1617,18 +1725,22 @@
      * @param subId The subId of the subscription to register for satellite modem state changed.
      * @param callback The callback to handle the satellite modem state changed event.
      *
-     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
-    @SatelliteManager.SatelliteError public int registerForSatelliteModemStateChanged(int subId,
-            @NonNull ISatelliteStateCallback callback) {
+    @SatelliteManager.SatelliteResult public int registerForSatelliteModemStateChanged(int subId,
+            @NonNull ISatelliteModemStateCallback callback) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("registerForSatelliteModemStateChanged: oemEnabledSatelliteFlag is disabled");
+            return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
+        }
         if (mSatelliteSessionController != null) {
             mSatelliteSessionController.registerForSatelliteModemStateChanged(callback);
         } else {
             loge("registerForSatelliteModemStateChanged: mSatelliteSessionController"
                     + " is not initialized yet");
-            return SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+            return SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
         }
-        return SatelliteManager.SATELLITE_ERROR_NONE;
+        return SATELLITE_RESULT_SUCCESS;
     }
 
     /**
@@ -1637,14 +1749,18 @@
      *
      * @param subId The subId of the subscription to unregister for satellite modem state changed.
      * @param callback The callback that was passed to
-     *                 {@link #registerForSatelliteModemStateChanged(int, ISatelliteStateCallback)}.
+     * {@link #registerForSatelliteModemStateChanged(int, ISatelliteModemStateCallback)}.
      */
-    public void unregisterForSatelliteModemStateChanged(int subId,
-            @NonNull ISatelliteStateCallback callback) {
+    public void unregisterForModemStateChanged(int subId,
+            @NonNull ISatelliteModemStateCallback callback) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("unregisterForModemStateChanged: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
         if (mSatelliteSessionController != null) {
             mSatelliteSessionController.unregisterForSatelliteModemStateChanged(callback);
         } else {
-            loge("registerForSatelliteModemStateChanged: mSatelliteSessionController"
+            loge("unregisterForModemStateChanged: mSatelliteSessionController"
                     + " is not initialized yet");
         }
     }
@@ -1655,10 +1771,18 @@
      * @param subId The subId of the subscription to register for incoming satellite datagrams.
      * @param callback The callback to handle incoming datagrams over satellite.
      *
-     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
-    @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId,
+    @SatelliteManager.SatelliteResult public int registerForIncomingDatagram(int subId,
             @NonNull ISatelliteDatagramCallback callback) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("registerForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
+            return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
+        }
+        if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
+        }
+        logd("registerForIncomingDatagram: callback=" + callback);
         return mDatagramController.registerForSatelliteDatagram(subId, callback);
     }
 
@@ -1668,10 +1792,18 @@
      *
      * @param subId The subId of the subscription to unregister for incoming satellite datagrams.
      * @param callback The callback that was passed to
-     *                 {@link #registerForSatelliteDatagram(int, ISatelliteDatagramCallback)}.
+     *                 {@link #registerForIncomingDatagram(int, ISatelliteDatagramCallback)}.
      */
-    public void unregisterForSatelliteDatagram(int subId,
+    public void unregisterForIncomingDatagram(int subId,
             @NonNull ISatelliteDatagramCallback callback) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("unregisterForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+        if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            return;
+        }
+        logd("unregisterForIncomingDatagram: callback=" + callback);
         mDatagramController.unregisterForSatelliteDatagram(subId, callback);
     }
 
@@ -1684,18 +1816,13 @@
      * long, SatelliteDatagram, int, Consumer)}
      *
      * @param subId The subId of the subscription used for receiving datagrams.
-     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
-    public void pollPendingSatelliteDatagrams(int subId, @NonNull IIntegerConsumer callback) {
+    public void pollPendingDatagrams(int subId, @NonNull IIntegerConsumer callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
-
-        Boolean satelliteProvisioned = isSatelliteProvisioned();
-        if (satelliteProvisioned == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteProvisioned) {
-            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.accept(error);
             return;
         }
 
@@ -1717,20 +1844,18 @@
      *                 Datagram will be passed down to modem without any encoding or encryption.
      * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
      *                                 full screen mode.
-     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
-    public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
+    public void sendDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
             SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull IIntegerConsumer callback) {
-        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
+        logd("sendSatelliteDatagram: subId: " + subId + " datagramType: " + datagramType
+                + " needFullScreenPointingUI: " + needFullScreenPointingUI);
 
-        Boolean satelliteProvisioned = isSatelliteProvisioned();
-        if (satelliteProvisioned == null) {
-            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteProvisioned) {
-            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.accept(error);
             return;
         }
 
@@ -1757,18 +1882,13 @@
      */
     public void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId,
             @NonNull ResultReceiver result) {
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+        int error = evaluateOemSatelliteRequestAllowed(false);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.send(error, null);
             return;
         }
 
-        sendRequestAsync(
-                CMD_IS_SATELLITE_COMMUNICATION_ALLOWED, result, SatelliteServiceUtils.getPhone());
+        sendRequestAsync(CMD_IS_SATELLITE_COMMUNICATION_ALLOWED, result, null);
     }
 
     /**
@@ -1779,28 +1899,13 @@
      *               be visible if the request is successful or an error code if the request failed.
      */
     public void requestTimeForNextSatelliteVisibility(int subId, @NonNull ResultReceiver result) {
-        Boolean satelliteSupported = isSatelliteSupportedInternal();
-        if (satelliteSupported == null) {
-            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-            return;
-        }
-        if (!satelliteSupported) {
-            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.send(error, null);
             return;
         }
 
-        Boolean satelliteProvisioned = isSatelliteProvisioned();
-        if (satelliteProvisioned == null) {
-            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
-            return;
-        }
-        if (!satelliteProvisioned) {
-            result.send(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED, null);
-            return;
-        }
-
-        Phone phone = SatelliteServiceUtils.getPhone();
-        sendRequestAsync(CMD_GET_TIME_SATELLITE_NEXT_VISIBLE, result, phone);
+        sendRequestAsync(CMD_GET_TIME_SATELLITE_NEXT_VISIBLE, result, null);
     }
 
     /**
@@ -1809,8 +1914,227 @@
      * @param subId The subId of the subscription.
      * @param isAligned {@true} means device is aligned with the satellite, otherwise {@false}.
      */
-    public void onDeviceAlignedWithSatellite(@NonNull int subId, @NonNull boolean isAligned) {
-        mDatagramController.onDeviceAlignedWithSatellite(isAligned);
+    public void setDeviceAlignedWithSatellite(@NonNull int subId, @NonNull boolean isAligned) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setDeviceAlignedWithSatellite: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+        mDatagramController.setDeviceAlignedWithSatellite(isAligned);
+    }
+
+    /**
+     * Add a restriction reason for disallowing carrier supported satellite plmn scan and attach
+     * by modem. After updating restriction list, evaluate if satellite should be enabled/disabled,
+     * and request modem to enable/disable satellite accordingly if the desired state does not match
+     * the current state.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param reason Reason for disallowing satellite communication for carrier.
+     * @param callback The callback to get the result of the request.
+     */
+    public void addAttachRestrictionForCarrier(int subId,
+            @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
+            @NonNull IIntegerConsumer callback) {
+        if (DBG) logd("addAttachRestrictionForCarrier(" + subId + ", " + reason + ")");
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            result.accept(SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+            logd("addAttachRestrictionForCarrier: carrierEnabledSatelliteFlag is "
+                    + "disabled");
+            return;
+        }
+
+        synchronized (mIsSatelliteEnabledLock) {
+            if (mSatelliteAttachRestrictionForCarrierArray.getOrDefault(
+                    subId, Collections.emptySet()).isEmpty()) {
+                mSatelliteAttachRestrictionForCarrierArray.put(subId, new HashSet<>());
+            } else if (mSatelliteAttachRestrictionForCarrierArray.get(subId).contains(reason)) {
+                result.accept(SATELLITE_RESULT_SUCCESS);
+                return;
+            }
+            mSatelliteAttachRestrictionForCarrierArray.get(subId).add(reason);
+        }
+        RequestHandleSatelliteAttachRestrictionForCarrierArgument request =
+                new RequestHandleSatelliteAttachRestrictionForCarrierArgument(subId, reason,
+                        result);
+        sendRequestAsync(CMD_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE, request,
+                SatelliteServiceUtils.getPhone(subId));
+    }
+
+    /**
+     * Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach
+     * by modem. After updating restriction list, evaluate if satellite should be enabled/disabled,
+     * and request modem to enable/disable satellite accordingly if the desired state does not match
+     * the current state.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param reason Reason for disallowing satellite communication.
+     * @param callback The callback to get the result of the request.
+     */
+    public void removeAttachRestrictionForCarrier(int subId,
+            @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
+            @NonNull IIntegerConsumer callback) {
+        if (DBG) logd("removeAttachRestrictionForCarrier(" + subId + ", " + reason + ")");
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            result.accept(SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+            logd("removeAttachRestrictionForCarrier: carrierEnabledSatelliteFlag is "
+                    + "disabled");
+            return;
+        }
+
+        synchronized (mIsSatelliteEnabledLock) {
+            if (mSatelliteAttachRestrictionForCarrierArray.getOrDefault(
+                    subId, Collections.emptySet()).isEmpty()
+                    || !mSatelliteAttachRestrictionForCarrierArray.get(subId).contains(reason)) {
+                result.accept(SATELLITE_RESULT_SUCCESS);
+                return;
+            }
+            mSatelliteAttachRestrictionForCarrierArray.get(subId).remove(reason);
+        }
+        RequestHandleSatelliteAttachRestrictionForCarrierArgument request =
+                new RequestHandleSatelliteAttachRestrictionForCarrierArgument(subId, reason,
+                        result);
+        sendRequestAsync(CMD_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE, request,
+                SatelliteServiceUtils.getPhone(subId));
+    }
+
+    /**
+     * Get reasons for disallowing satellite communication, as requested by
+     * {@link #addAttachRestrictionForCarrier(int, int, IIntegerConsumer)}.
+     *
+     * @param subId The subId of the subscription to request for.
+     *
+     * @return Set of reasons for disallowing satellite attach for carrier.
+     */
+    @NonNull public Set<Integer> getAttachRestrictionReasonsForCarrier(int subId) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("getAttachRestrictionReasonsForCarrier: carrierEnabledSatelliteFlag is "
+                    + "disabled");
+            return new HashSet<>();
+        }
+        synchronized (mIsSatelliteEnabledLock) {
+            Set<Integer> resultSet =
+                    mSatelliteAttachRestrictionForCarrierArray.get(subId);
+            if (resultSet == null) {
+                return new HashSet<>();
+            }
+            return new HashSet<>(resultSet);
+        }
+    }
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param result Result receiver to get the error code of the request and the current signal
+     * strength of the satellite connection.
+     */
+    public void requestNtnSignalStrength(int subId, @NonNull ResultReceiver result) {
+        if (DBG) logd("requestNtnSignalStrength()");
+
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) {
+            result.send(error, null);
+            return;
+        }
+
+        /* In case cache is available, it is not needed to request non-terrestrial signal strength
+        to modem */
+        synchronized (mNtnSignalsStrengthLock) {
+            if (mNtnSignalStrength.getLevel() != NTN_SIGNAL_STRENGTH_NONE) {
+                Bundle bundle = new Bundle();
+                bundle.putParcelable(KEY_NTN_SIGNAL_STRENGTH, mNtnSignalStrength);
+                result.send(SATELLITE_RESULT_SUCCESS, bundle);
+                return;
+            }
+        }
+
+        Phone phone = SatelliteServiceUtils.getPhone();
+        sendRequestAsync(CMD_REQUEST_NTN_SIGNAL_STRENGTH, result, phone);
+    }
+
+    /**
+     * Registers for NTN signal strength changed from satellite modem. If the registration operation
+     * is not successful, a {@link ServiceSpecificException} that contains
+     * {@link SatelliteManager.SatelliteResult} will be thrown.
+     *
+     * @param subId The id of the subscription to request for.
+     * @param callback The callback to handle the NTN signal strength changed event. If the
+     * operation is successful, {@link INtnSignalStrengthCallback#onNtnSignalStrengthChanged(
+     * NtnSignalStrength)} will return an instance of {@link NtnSignalStrength} with a value of
+     * {@link NtnSignalStrength.NtnSignalStrengthLevel} when the signal strength of non-terrestrial
+     * network has changed.
+     *
+     * @throws ServiceSpecificException If the callback registration operation fails.
+     */
+    public void registerForNtnSignalStrengthChanged(int subId,
+            @NonNull INtnSignalStrengthCallback callback) throws RemoteException {
+        if (DBG) logd("registerForNtnSignalStrengthChanged()");
+
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error == SATELLITE_RESULT_SUCCESS) {
+            mNtnSignalStrengthChangedListeners.put(callback.asBinder(), callback);
+        } else {
+            throw new RemoteException(new IllegalStateException("registration fails: " + error));
+        }
+    }
+
+    /**
+     * Unregisters for NTN signal strength changed from satellite modem.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The id of the subscription to unregister for listening NTN signal strength
+     * changed event.
+     * @param callback The callback that was passed to
+     * {@link #registerForNtnSignalStrengthChanged(int, INtnSignalStrengthCallback)}
+     */
+    public void unregisterForNtnSignalStrengthChanged(
+            int subId, @NonNull INtnSignalStrengthCallback callback) {
+        if (DBG) logd("unregisterForNtnSignalStrengthChanged()");
+
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error == SATELLITE_RESULT_SUCCESS) {
+            mNtnSignalStrengthChangedListeners.remove(callback.asBinder());
+        }
+    }
+
+    /**
+     * Registers for satellite capabilities change event from the satellite service.
+     *
+     * @param subId The id of the subscription to request for.
+     * @param callback The callback to handle the satellite capabilities changed event.
+     *
+     * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
+     */
+    @SatelliteManager.SatelliteResult public int registerForCapabilitiesChanged(
+            int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+        if (DBG) logd("registerForCapabilitiesChanged()");
+
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) return error;
+
+        mSatelliteCapabilitiesChangedListeners.put(callback.asBinder(), callback);
+        return SATELLITE_RESULT_SUCCESS;
+    }
+
+    /**
+     * Unregisters for satellite capabilities change event from the satellite service.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The id of the subscription to unregister for listening satellite capabilities
+     * changed event.
+     * @param callback The callback that was passed to
+     * {@link #registerForCapabilitiesChanged(int, ISatelliteCapabilitiesCallback)}
+     */
+    public void unregisterForCapabilitiesChanged(
+            int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+        if (DBG) logd("unregisterForCapabilitiesChanged()");
+
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error == SATELLITE_RESULT_SUCCESS) {
+            mSatelliteCapabilitiesChangedListeners.remove(callback.asBinder());
+        }
     }
 
     /**
@@ -1821,15 +2145,22 @@
      * {@code false} otherwise.
      */
     public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) {
-        if (!isMockModemAllowed()) return false;
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setSatelliteServicePackageName: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+        if (!isMockModemAllowed()) {
+            logd("setSatelliteServicePackageName: mock modem not allowed");
+            return false;
+        }
 
         // Cached states need to be cleared whenever switching satellite vendor services.
         logd("setSatelliteServicePackageName: Resetting cached states");
         synchronized (mIsSatelliteSupportedLock) {
             mIsSatelliteSupported = null;
         }
-        synchronized (mIsSatelliteProvisionedLock) {
-            mIsSatelliteProvisioned = null;
+        synchronized (mSatelliteViaOemProvisionLock) {
+            mIsSatelliteViaOemProvisioned = null;
         }
         synchronized (mIsSatelliteEnabledLock) {
             mIsSatelliteEnabled = null;
@@ -1850,6 +2181,10 @@
      * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
      */
     public boolean setSatelliteListeningTimeoutDuration(long timeoutMillis) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setSatelliteListeningTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
         if (mSatelliteSessionController == null) {
             loge("mSatelliteSessionController is not initialized yet");
             return false;
@@ -1858,14 +2193,56 @@
     }
 
     /**
-     * This API can be used by only CTS to update the timeout duration in milliseconds whether
-     * the device is aligned with the satellite for demo mode
+     * This API can be used by only CTS to override timeout durations used by DatagramController
+     * module.
      *
      * @param timeoutMillis The timeout duration in millisecond.
      * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
      */
-    public boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) {
-        return mDatagramController.setSatelliteDeviceAlignedTimeoutDuration(timeoutMillis);
+    public boolean setDatagramControllerTimeoutDuration(
+            boolean reset, int timeoutType, long timeoutMillis) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setDatagramControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+        logd("setDatagramControllerTimeoutDuration: reset=" + reset + ", timeoutType="
+                + timeoutType + ", timeoutMillis=" + timeoutMillis);
+        return mDatagramController.setDatagramControllerTimeoutDuration(
+                reset, timeoutType, timeoutMillis);
+    }
+
+    /**
+     * This API can be used by only CTS to override timeout durations used by SatelliteController
+     * module.
+     *
+     * @param timeoutMillis The timeout duration in millisecond.
+     * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+     */
+    public boolean setSatelliteControllerTimeoutDuration(
+            boolean reset, int timeoutType, long timeoutMillis) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setSatelliteControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+        if (!isMockModemAllowed()) {
+            logd("setSatelliteControllerTimeoutDuration: mock modem is not allowed");
+            return false;
+        }
+        logd("setSatelliteControllerTimeoutDuration: reset=" + reset + ", timeoutType="
+                + timeoutType + ", timeoutMillis=" + timeoutMillis);
+        if (timeoutType == TIMEOUT_TYPE_WAIT_FOR_SATELLITE_ENABLING_RESPONSE) {
+            if (reset) {
+                mWaitTimeForSatelliteEnablingResponse =
+                        getWaitForSatelliteEnablingResponseTimeoutMillis();
+            } else {
+                mWaitTimeForSatelliteEnablingResponse = timeoutMillis;
+            }
+            logd("mWaitTimeForSatelliteEnablingResponse=" + mWaitTimeForSatelliteEnablingResponse);
+        } else {
+            logw("Invalid timeoutType=" + timeoutType);
+            return false;
+        }
+        return true;
     }
 
     /**
@@ -1876,6 +2253,10 @@
      * {@code false} otherwise.
      */
     public boolean setSatelliteGatewayServicePackageName(@Nullable String servicePackageName) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setSatelliteGatewayServicePackageName: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
         if (mSatelliteSessionController == null) {
             loge("mSatelliteSessionController is not initialized yet");
             return false;
@@ -1894,10 +2275,84 @@
      */
     public boolean setSatellitePointingUiClassName(
             @Nullable String packageName, @Nullable String className) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setSatellitePointingUiClassName: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
         return mPointingAppController.setSatellitePointingUiClassName(packageName, className);
     }
 
     /**
+     * This API can be used in only testing to override connectivity status in monitoring emergency
+     * calls and sending EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer.
+     *
+     * @param handoverType The type of handover from emergency call to satellite messaging. Use one
+     *                     of the following values to enable the override:
+     *                     0 - EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS
+     *                     1 - EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911
+     *                     To disable the override, use -1 for handoverType.
+     * @param delaySeconds The event EVENT_DISPLAY_EMERGENCY_MESSAGE will be sent to Dialer
+     *                     delaySeconds after the emergency call starts.
+     * @return {@code true} if the handover type is set successfully, {@code false} otherwise.
+     */
+    public boolean setEmergencyCallToSatelliteHandoverType(int handoverType, int delaySeconds) {
+        if (!isMockModemAllowed()) {
+            loge("setEmergencyCallToSatelliteHandoverType: mock modem not allowed");
+            return false;
+        }
+        if (isHandoverTypeValid(handoverType)) {
+            mEnforcedEmergencyCallToSatelliteHandoverType = handoverType;
+            mDelayInSendingEventDisplayEmergencyMessage = delaySeconds > 0 ? delaySeconds : 0;
+        } else {
+            mEnforcedEmergencyCallToSatelliteHandoverType =
+                    INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
+            mDelayInSendingEventDisplayEmergencyMessage = 0;
+        }
+        return true;
+    }
+
+    /**
+     * This API can be used in only testing to override oem-enabled satellite provision status.
+     *
+     * @param reset {@code true} mean the overriding status should not be used, {@code false}
+     *              otherwise.
+     * @param isProvisioned The overriding provision status.
+     * @return {@code true} if the provision status is set successfully, {@code false} otherwise.
+     */
+    public boolean setOemEnabledSatelliteProvisionStatus(boolean reset, boolean isProvisioned) {
+        if (!isMockModemAllowed()) {
+            loge("setOemEnabledSatelliteProvisionStatus: mock modem not allowed");
+            return false;
+        }
+        synchronized (mSatelliteViaOemProvisionLock) {
+            if (reset) {
+                mOverriddenIsSatelliteViaOemProvisioned = null;
+            } else {
+                mOverriddenIsSatelliteViaOemProvisioned = isProvisioned;
+            }
+        }
+        return true;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected int getEnforcedEmergencyCallToSatelliteHandoverType() {
+        return mEnforcedEmergencyCallToSatelliteHandoverType;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected int getDelayInSendingEventDisplayEmergencyMessage() {
+        return mDelayInSendingEventDisplayEmergencyMessage;
+    }
+
+    private boolean isHandoverTypeValid(int handoverType) {
+        if (handoverType == EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS
+                || handoverType == EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * This function is used by {@link SatelliteModemInterface} to notify
      * {@link SatelliteController} that the satellite vendor service was just connected.
      * <p>
@@ -1910,6 +2365,10 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void onSatelliteServiceConnected() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("onSatelliteServiceConnected: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
         if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
             synchronized (mIsSatelliteSupportedLock) {
                 if (mIsSatelliteSupported == null) {
@@ -1917,8 +2376,8 @@
                         @Override
                         protected void onReceiveResult(
                                 int resultCode, Bundle resultData) {
-                            logd("requestIsSatelliteSupported: resultCode="
-                                    + resultCode);
+                            logd("onSatelliteServiceConnected.requestIsSatelliteSupported:"
+                                    + " resultCode=" + resultCode);
                         }
                     };
                     requestIsSatelliteSupported(
@@ -1932,33 +2391,71 @@
     }
 
     /**
-     * @return {@code true} is satellite is supported on the device, {@code  false} otherwise.
+     * This function is used by {@link com.android.internal.telephony.ServiceStateTracker} to notify
+     * {@link SatelliteController} that it has received a request to power off the cellular radio
+     * modem. {@link SatelliteController} will then power off the satellite modem.
      */
-    public boolean isSatelliteSupported() {
-        Boolean supported = isSatelliteSupportedInternal();
+    public void onCellularRadioPowerOffRequested() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("onCellularRadioPowerOffRequested: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        mIsRadioOn = false;
+        requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                false /* enableSatellite */, false /* enableDemoMode */,
+                new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        logd("onRadioPowerOffRequested: requestSatelliteEnabled result=" + result);
+                    }
+                });
+    }
+
+    /**
+     * @return {@code true} if satellite is supported via OEM on the device,
+     * {@code  false} otherwise.
+     */
+    public boolean isSatelliteSupportedViaOem() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("isSatelliteSupported: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+        Boolean supported = isSatelliteSupportedViaOemInternal();
         return (supported != null ? supported : false);
     }
 
     /**
+     * @param subId Subscription ID.
      * @return The list of satellite PLMNs used for connecting to satellite networks.
      */
     @NonNull
-    public List<String> getSatellitePlmnList() {
-        return new ArrayList<>(mSatellitePlmnList);
+    public List<String> getSatellitePlmnsForCarrier(int subId) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("getSatellitePlmnsForCarrier: carrierEnabledSatelliteFlag is disabled");
+            return new ArrayList<>();
+        }
+        synchronized (mSupportedSatelliteServicesLock) {
+            return mMergedPlmnListPerCarrier.get(subId, new ArrayList<>()).stream().toList();
+        }
     }
 
     /**
      * @param subId Subscription ID.
-     * @param plmn The satellite roaming plmn.
+     * @param plmn The satellite plmn.
      * @return The list of services supported by the carrier associated with the {@code subId} for
      * the satellite network {@code plmn}.
      */
     @NonNull
     public List<Integer> getSupportedSatelliteServices(int subId, String plmn) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("getSupportedSatelliteServices: carrierEnabledSatelliteFlag is disabled");
+            return new ArrayList<>();
+        }
         synchronized (mSupportedSatelliteServicesLock) {
-            if (mSupportedSatelliteServices.containsKey(subId)) {
+            if (mSatelliteServicesSupportedByCarriers.containsKey(subId)) {
                 Map<String, Set<Integer>> supportedServices =
-                        mSupportedSatelliteServices.get(subId);
+                        mSatelliteServicesSupportedByCarriers.get(subId);
                 if (supportedServices != null && supportedServices.containsKey(plmn)) {
                     return new ArrayList<>(supportedServices.get(plmn));
                 } else {
@@ -1966,18 +2463,181 @@
                             + "does not contain key plmn=" + plmn);
                 }
             } else {
-                loge("getSupportedSatelliteServices: mSupportedSatelliteServices does contain key"
-                        + " subId=" + subId);
+                loge("getSupportedSatelliteServices: mSatelliteServicesSupportedByCarriers does "
+                        + "not contain key subId=" + subId);
             }
             return new ArrayList<>();
         }
     }
 
     /**
+     * Check whether satellite modem has to attach to a satellite network before sending/receiving
+     * datagrams.
+     *
+     * @return {@code true} if satellite attach is required, {@code false} otherwise.
+     */
+    public boolean isSatelliteAttachRequired() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("isSatelliteAttachRequired: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+
+        synchronized (mSatelliteCapabilitiesLock) {
+            if (mSatelliteCapabilities == null) {
+                loge("isSatelliteAttachRequired: mSatelliteCapabilities is null");
+                return false;
+            }
+            if (mSatelliteCapabilities.getSupportedRadioTechnologies().contains(
+                    SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * @return {@code true} if satellite is supported via carrier by any subscription on the device,
+     * {@code false} otherwise.
+     */
+    public boolean isSatelliteSupportedViaCarrier() {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("isSatelliteSupportedViaCarrier: carrierEnabledSatelliteFlag is disabled");
+            return false;
+        }
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (isSatelliteSupportedViaCarrier(phone.getSubId())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return {@code true} if any subscription on the device is connected to satellite,
+     * {@code false} otherwise.
+     */
+    private boolean isUsingNonTerrestrialNetworkViaCarrier() {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("isUsingNonTerrestrialNetwork: carrierEnabledSatelliteFlag is disabled");
+            return false;
+        }
+        for (Phone phone : PhoneFactory.getPhones()) {
+            ServiceState serviceState = phone.getServiceState();
+            if (serviceState != null && serviceState.isUsingNonTerrestrialNetwork()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return {@code true} if the device is connected to satellite via any carrier within the
+     * {@link CarrierConfigManager#KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT}
+     * duration, {@code false} otherwise.
+     */
+    public boolean isSatelliteConnectedViaCarrierWithinHysteresisTime() {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("isSatelliteConnectedViaCarrierWithinHysteresisTime: carrierEnabledSatelliteFlag"
+                    + " is disabled");
+            return false;
+        }
+        if (isUsingNonTerrestrialNetworkViaCarrier()) {
+            return true;
+        }
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (isSatelliteSupportedViaCarrier(phone.getSubId())) {
+                synchronized (mSatelliteConnectedLock) {
+                    Boolean isHysteresisTimeExpired =
+                            mIsSatelliteConnectedViaCarrierHysteresisTimeExpired.get(
+                                    phone.getSubId());
+                    if (isHysteresisTimeExpired != null && isHysteresisTimeExpired) {
+                        continue;
+                    }
+
+                    Long lastDisconnectedTime =
+                            mLastSatelliteDisconnectedTimesMillis.get(phone.getSubId());
+                    long satelliteConnectionHysteresisTime =
+                            getSatelliteConnectionHysteresisTimeMillis(phone.getSubId());
+                    if (lastDisconnectedTime != null
+                            && (getElapsedRealtime() - lastDisconnectedTime)
+                            <= satelliteConnectionHysteresisTime) {
+                        return true;
+                    } else {
+                        mIsSatelliteConnectedViaCarrierHysteresisTimeExpired.put(
+                                phone.getSubId(), true);
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected long getElapsedRealtime() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * To use the satellite service, update the EntitlementStatus and the PlmnAllowedList after
+     * receiving the satellite configuration from the entitlement server. If satellite
+     * entitlement is enabled, enable satellite for the carrier. Otherwise, disable satellite.
+     *
+     * @param subId              subId
+     * @param entitlementEnabled {@code true} Satellite service enabled
+     * @param allowedPlmnList    plmn allowed list to use the satellite service
+     * @param callback           callback for accept
+     */
+    public void onSatelliteEntitlementStatusUpdated(int subId, boolean entitlementEnabled,
+            List<String> allowedPlmnList, @Nullable IIntegerConsumer callback) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            return;
+        }
+
+        if (callback == null) {
+            callback = new IIntegerConsumer.Stub() {
+                @Override
+                public void accept(int result) {
+                    logd("updateSatelliteEntitlementStatus:" + result);
+                }
+            };
+        }
+        logd("onSatelliteEntitlementStatusUpdated subId=" + subId + " , entitlementEnabled="
+                + entitlementEnabled + ", allowedPlmnList=" + (Objects.equals(null, allowedPlmnList)
+                ? "" : allowedPlmnList + ""));
+
+        synchronized (mSupportedSatelliteServicesLock) {
+            if (mSatelliteEntitlementStatusPerCarrier.get(subId, false) != entitlementEnabled) {
+                logd("update the carrier satellite enabled to " + entitlementEnabled);
+                mSatelliteEntitlementStatusPerCarrier.put(subId, entitlementEnabled);
+                try {
+                    mSubscriptionManagerService.setSubscriptionProperty(subId,
+                            SATELLITE_ENTITLEMENT_STATUS, entitlementEnabled ? "1" : "0");
+                } catch (IllegalArgumentException | SecurityException e) {
+                    loge("onSatelliteEntitlementStatusUpdated: setSubscriptionProperty, e=" + e);
+                }
+            }
+            mMergedPlmnListPerCarrier.remove(subId);
+
+            mEntitlementPlmnListPerCarrier.put(subId, allowedPlmnList);
+            updatePlmnListPerCarrier(subId);
+            configureSatellitePlmnForCarrier(subId);
+            mSubscriptionManagerService.setSatelliteEntitlementPlmnList(subId, allowedPlmnList);
+
+            if (mSatelliteEntitlementStatusPerCarrier.get(subId, false)) {
+                removeAttachRestrictionForCarrier(subId,
+                        SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT, callback);
+            } else {
+                addAttachRestrictionForCarrier(subId,
+                        SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT, callback);
+            }
+        }
+    }
+
+    /**
      * If we have not successfully queried the satellite modem for its satellite service support,
      * we will retry the query one more time. Otherwise, we will return the cached result.
      */
-    private Boolean isSatelliteSupportedInternal() {
+    private Boolean isSatelliteSupportedViaOemInternal() {
         synchronized (mIsSatelliteSupportedLock) {
             if (mIsSatelliteSupported != null) {
                 /* We have already successfully queried the satellite modem. */
@@ -1992,7 +2652,8 @@
                 new ResultReceiver(this) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        logd("requestIsSatelliteSupported: resultCode=" + resultCode);
+                        logd("isSatelliteSupportedViaOemInternal.requestIsSatelliteSupported:"
+                                + " resultCode=" + resultCode);
                     }
                 });
         return null;
@@ -2000,7 +2661,7 @@
 
     private void handleEventProvisionSatelliteServiceDone(
             @NonNull ProvisionSatelliteServiceArgument arg,
-            @SatelliteManager.SatelliteError int result) {
+            @SatelliteManager.SatelliteResult int result) {
         logd("handleEventProvisionSatelliteServiceDone: result="
                 + result + ", subId=" + arg.subId);
 
@@ -2009,19 +2670,29 @@
             loge("handleEventProvisionSatelliteServiceDone: callback is null for subId="
                     + arg.subId);
             mProvisionMetricsStats
-                    .setResultCode(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)
+                    .setResultCode(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE)
                     .setIsProvisionRequest(true)
                     .reportProvisionMetrics();
             mControllerMetricsStats.reportProvisionCount(
-                    SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                    SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
             return;
         }
-        callback.accept(result);
+        if (result == SATELLITE_RESULT_SUCCESS
+                || result == SATELLITE_RESULT_REQUEST_NOT_SUPPORTED) {
+            persistOemEnabledSatelliteProvisionStatus(true);
+            synchronized (mSatelliteViaOemProvisionLock) {
+                mIsSatelliteViaOemProvisioned = true;
+            }
+            callback.accept(SATELLITE_RESULT_SUCCESS);
+            handleEventSatelliteProvisionStateChanged(true);
+        } else {
+            callback.accept(result);
+        }
     }
 
     private void handleEventDeprovisionSatelliteServiceDone(
             @NonNull ProvisionSatelliteServiceArgument arg,
-            @SatelliteManager.SatelliteError int result) {
+            @SatelliteManager.SatelliteResult int result) {
         if (arg == null) {
             loge("handleEventDeprovisionSatelliteServiceDone: arg is null");
             return;
@@ -2029,13 +2700,23 @@
         logd("handleEventDeprovisionSatelliteServiceDone: result="
                 + result + ", subId=" + arg.subId);
 
-        if (arg.callback != null) {
+        if (result == SATELLITE_RESULT_SUCCESS
+                || result == SATELLITE_RESULT_REQUEST_NOT_SUPPORTED) {
+            persistOemEnabledSatelliteProvisionStatus(false);
+            synchronized (mSatelliteViaOemProvisionLock) {
+                mIsSatelliteViaOemProvisioned = false;
+            }
+            if (arg.callback != null) {
+                arg.callback.accept(SATELLITE_RESULT_SUCCESS);
+            }
+            handleEventSatelliteProvisionStateChanged(false);
+        } else if (arg.callback != null) {
             arg.callback.accept(result);
-            mProvisionMetricsStats.setResultCode(result)
-                    .setIsProvisionRequest(false)
-                    .reportProvisionMetrics();
-            mControllerMetricsStats.reportDeprovisionCount(result);
         }
+        mProvisionMetricsStats.setResultCode(result)
+                .setIsProvisionRequest(false)
+                .reportProvisionMetrics();
+        mControllerMetricsStats.reportDeprovisionCount(result);
     }
 
     private void handleStartSatelliteTransmissionUpdatesDone(@NonNull AsyncResult ar) {
@@ -2046,12 +2727,12 @@
                 "handleStartSatelliteTransmissionUpdatesDone");
         arg.errorCallback.accept(errorCode);
 
-        if (errorCode != SatelliteManager.SATELLITE_ERROR_NONE) {
+        if (errorCode != SATELLITE_RESULT_SUCCESS) {
             mPointingAppController.setStartedSatelliteTransmissionUpdates(false);
             // We need to remove the callback from our listener list since the caller might not call
             // stopSatelliteTransmissionUpdates to unregister the callback in case of failure.
             mPointingAppController.unregisterForSatelliteTransmissionUpdates(arg.subId,
-                    arg.errorCallback, arg.callback, request.phone);
+                    arg.errorCallback, arg.callback);
         } else {
             mPointingAppController.setStartedSatelliteTransmissionUpdates(true);
         }
@@ -2108,10 +2789,14 @@
      * @return true if satellite is provisioned on the given subscription else return false.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected Boolean isSatelliteProvisioned() {
-        synchronized (mIsSatelliteProvisionedLock) {
-            if (mIsSatelliteProvisioned != null) {
-                return mIsSatelliteProvisioned;
+    protected Boolean isSatelliteViaOemProvisioned() {
+        synchronized (mSatelliteViaOemProvisionLock) {
+            if (mOverriddenIsSatelliteViaOemProvisioned != null) {
+                return mOverriddenIsSatelliteViaOemProvisioned;
+            }
+
+            if (mIsSatelliteViaOemProvisioned != null) {
+                return mIsSatelliteViaOemProvisioned;
             }
         }
 
@@ -2119,7 +2804,7 @@
                 new ResultReceiver(this) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        logd("requestIsSatelliteProvisioned: resultCode=" + resultCode);
+                        logd("isSatelliteViaOemProvisioned: resultCode=" + resultCode);
                     }
                 });
         return null;
@@ -2128,10 +2813,7 @@
     private void handleSatelliteEnabled(SatelliteControllerHandlerRequest request) {
         RequestSatelliteEnabledArgument argument =
                 (RequestSatelliteEnabledArgument) request.argument;
-        Phone phone = request.phone;
-
-        if (!argument.enableSatellite && (mSatelliteModemInterface.isSatelliteServiceSupported()
-                || phone != null)) {
+        if (!argument.enableSatellite && mSatelliteModemInterface.isSatelliteServiceSupported()) {
             synchronized (mIsSatelliteEnabledLock) {
                 mWaitingForDisableSatelliteModemResponse = true;
                 mWaitingForSatelliteModemOff = true;
@@ -2139,18 +2821,24 @@
         }
 
         Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request);
-        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-            mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite,
-                    argument.enableDemoMode, onCompleted);
-            return;
+        mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite,
+                argument.enableDemoMode, onCompleted);
+        startWaitForSatelliteEnablingResponseTimer(argument);
+    }
+
+    private void handleRequestSatelliteAttachRestrictionForCarrierCmd(
+            SatelliteControllerHandlerRequest request) {
+        RequestHandleSatelliteAttachRestrictionForCarrierArgument argument =
+                (RequestHandleSatelliteAttachRestrictionForCarrierArgument) request.argument;
+
+        if (argument.reason == SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER) {
+            if (!persistSatelliteAttachEnabledForCarrierSetting(argument.subId)) {
+                argument.callback.accept(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
+                return;
+            }
         }
 
-        if (phone != null) {
-            phone.setSatellitePower(onCompleted, argument.enableSatellite);
-        } else {
-            loge("requestSatelliteEnabled: No phone object");
-            argument.callback.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-        }
+        evaluateEnablingSatelliteForCarrier(argument.subId, argument.reason, argument.callback);
     }
 
     private void updateSatelliteSupportedStateWhenSatelliteServiceConnected(boolean supported) {
@@ -2163,12 +2851,15 @@
             registerForSatelliteProvisionStateChanged();
             registerForPendingDatagramCount();
             registerForSatelliteModemStateChanged();
+            registerForNtnSignalStrengthChanged();
+            registerForCapabilitiesChanged();
 
             requestIsSatelliteProvisioned(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
                     new ResultReceiver(this) {
                         @Override
                         protected void onReceiveResult(int resultCode, Bundle resultData) {
-                            logd("requestIsSatelliteProvisioned: resultCode=" + resultCode);
+                            logd("requestIsSatelliteProvisioned: resultCode=" + resultCode
+                                    + ", resultData=" + resultData);
                             requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
                                     false, false,
                                     new IIntegerConsumer.Stub() {
@@ -2183,7 +2874,8 @@
                     new ResultReceiver(this) {
                         @Override
                         protected void onReceiveResult(int resultCode, Bundle resultData) {
-                            logd("requestSatelliteCapabilities: resultCode=" + resultCode);
+                            logd("requestSatelliteCapabilities: resultCode=" + resultCode
+                                    + ", resultData=" + resultData);
                         }
                     });
         }
@@ -2208,15 +2900,6 @@
                         this, EVENT_SATELLITE_PROVISION_STATE_CHANGED, null);
                 mRegisteredForProvisionStateChangedWithSatelliteService.set(true);
             }
-        } else {
-            Phone phone = SatelliteServiceUtils.getPhone();
-            if (phone == null) {
-                loge("registerForSatelliteProvisionStateChanged: phone is null");
-            } else if (!mRegisteredForProvisionStateChangedWithPhone.get()) {
-                phone.registerForSatelliteProvisionStateChanged(
-                        this, EVENT_SATELLITE_PROVISION_STATE_CHANGED, null);
-                mRegisteredForProvisionStateChangedWithPhone.set(true);
-            }
         }
     }
 
@@ -2227,15 +2910,6 @@
                         this, EVENT_PENDING_DATAGRAMS, null);
                 mRegisteredForPendingDatagramCountWithSatelliteService.set(true);
             }
-        } else {
-            Phone phone = SatelliteServiceUtils.getPhone();
-            if (phone == null) {
-                loge("registerForPendingDatagramCount: satellite phone is "
-                        + "not initialized yet");
-            } else if (!mRegisteredForPendingDatagramCountWithPhone.get()) {
-                phone.registerForPendingDatagramCount(this, EVENT_PENDING_DATAGRAMS, null);
-                mRegisteredForPendingDatagramCountWithPhone.set(true);
-            }
         }
     }
 
@@ -2246,15 +2920,35 @@
                         this, EVENT_SATELLITE_MODEM_STATE_CHANGED, null);
                 mRegisteredForSatelliteModemStateChangedWithSatelliteService.set(true);
             }
-        } else {
-            Phone phone = SatelliteServiceUtils.getPhone();
-            if (phone == null) {
-                loge("registerForSatelliteModemStateChanged: satellite phone is "
-                        + "not initialized yet");
-            } else if (!mRegisteredForSatelliteModemStateChangedWithPhone.get()) {
-                phone.registerForSatelliteModemStateChanged(
-                        this, EVENT_SATELLITE_MODEM_STATE_CHANGED, null);
-                mRegisteredForSatelliteModemStateChangedWithPhone.set(true);
+        }
+    }
+
+    private void registerForNtnSignalStrengthChanged() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("registerForNtnSignalStrengthChanged: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            if (!mRegisteredForNtnSignalStrengthChanged.get()) {
+                mSatelliteModemInterface.registerForNtnSignalStrengthChanged(
+                        this, EVENT_NTN_SIGNAL_STRENGTH_CHANGED, null);
+                mRegisteredForNtnSignalStrengthChanged.set(true);
+            }
+        }
+    }
+
+    private void registerForCapabilitiesChanged() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("registerForCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            if (!mRegisteredForSatelliteCapabilitiesChanged.get()) {
+                mSatelliteModemInterface.registerForSatelliteCapabilitiesChanged(
+                        this, EVENT_SATELLITE_CAPABILITIES_CHANGED, null);
+                mRegisteredForSatelliteCapabilitiesChanged.set(true);
             }
         }
     }
@@ -2262,20 +2956,21 @@
     private void handleEventSatelliteProvisionStateChanged(boolean provisioned) {
         logd("handleSatelliteProvisionStateChangedEvent: provisioned=" + provisioned);
 
-        synchronized (mIsSatelliteProvisionedLock) {
-            mIsSatelliteProvisioned = provisioned;
+        synchronized (mSatelliteViaOemProvisionLock) {
+            persistOemEnabledSatelliteProvisionStatus(provisioned);
+            mIsSatelliteViaOemProvisioned = provisioned;
         }
 
-        List<ISatelliteProvisionStateCallback> toBeRemoved = new ArrayList<>();
+        List<ISatelliteProvisionStateCallback> deadCallersList = new ArrayList<>();
         mSatelliteProvisionStateChangedListeners.values().forEach(listener -> {
             try {
                 listener.onSatelliteProvisionStateChanged(provisioned);
             } catch (RemoteException e) {
                 logd("handleSatelliteProvisionStateChangedEvent RemoteException: " + e);
-                toBeRemoved.add(listener);
+                deadCallersList.add(listener);
             }
         });
-        toBeRemoved.forEach(listener -> {
+        deadCallersList.forEach(listener -> {
             mSatelliteProvisionStateChangedListeners.remove(listener.asBinder());
         });
     }
@@ -2290,8 +2985,8 @@
                         || ((mIsSatelliteEnabled == null || isSatelliteEnabled())
                         && !mWaitingForDisableSatelliteModemResponse)) {
                     int error = (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF)
-                            ? SatelliteManager.SATELLITE_ERROR_NONE
-                            : SatelliteManager.SATELLITE_INVALID_MODEM_STATE;
+                            ? SATELLITE_RESULT_SUCCESS
+                            : SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE;
                     Consumer<Integer> callback = null;
                     synchronized (mSatelliteEnabledRequestLock) {
                         if (mSatelliteEnabledRequest != null) {
@@ -2307,8 +3002,63 @@
                 }
                 mWaitingForSatelliteModemOff = false;
             }
+        } else {
+            if (mSatelliteSessionController != null) {
+                mSatelliteSessionController.onSatelliteModemStateChanged(state);
+            } else {
+                loge("handleEventSatelliteModemStateChanged: mSatelliteSessionController is null");
+            }
         }
-        mDatagramController.onSatelliteModemStateChanged(state);
+    }
+
+    private void handleEventNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength) {
+        logd("handleEventNtnSignalStrengthChanged: ntnSignalStrength=" + ntnSignalStrength);
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("handleEventNtnSignalStrengthChanged: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        synchronized (mNtnSignalsStrengthLock) {
+            mNtnSignalStrength = ntnSignalStrength;
+        }
+
+        List<INtnSignalStrengthCallback> deadCallersList = new ArrayList<>();
+        mNtnSignalStrengthChangedListeners.values().forEach(listener -> {
+            try {
+                listener.onNtnSignalStrengthChanged(ntnSignalStrength);
+            } catch (RemoteException e) {
+                logd("handleEventNtnSignalStrengthChanged RemoteException: " + e);
+                deadCallersList.add(listener);
+            }
+        });
+        deadCallersList.forEach(listener -> {
+            mNtnSignalStrengthChangedListeners.remove(listener.asBinder());
+        });
+    }
+
+    private void handleEventSatelliteCapabilitiesChanged(SatelliteCapabilities capabilities) {
+        logd("handleEventSatelliteCapabilitiesChanged()");
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("handleEventSatelliteCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        synchronized (mSatelliteCapabilitiesLock) {
+            mSatelliteCapabilities = capabilities;
+        }
+
+        List<ISatelliteCapabilitiesCallback> deadCallersList = new ArrayList<>();
+        mSatelliteCapabilitiesChangedListeners.values().forEach(listener -> {
+            try {
+                listener.onSatelliteCapabilitiesChanged(capabilities);
+            } catch (RemoteException e) {
+                logd("handleEventSatelliteCapabilitiesChanged RemoteException: " + e);
+                deadCallersList.add(listener);
+            }
+        });
+        deadCallersList.forEach(listener -> {
+            mSatelliteCapabilitiesChangedListeners.remove(listener.asBinder());
+        });
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -2343,7 +3093,7 @@
                 synchronized (mIsSatelliteEnabledLock) {
                     mIsSatelliteEnabled = mSatelliteEnabledRequest.enableSatellite;
                 }
-                mSatelliteEnabledRequest.callback.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+                mSatelliteEnabledRequest.callback.accept(SATELLITE_RESULT_SUCCESS);
                 updateSatelliteEnabledState(
                         mSatelliteEnabledRequest.enableSatellite,
                         "EVENT_SET_SATELLITE_ENABLED_DONE");
@@ -2362,7 +3112,7 @@
     }
 
     private void moveSatelliteToOffStateAndCleanUpResources(
-            @SatelliteManager.SatelliteError int error, @Nullable Consumer<Integer> callback) {
+            @SatelliteManager.SatelliteResult int error, @Nullable Consumer<Integer> callback) {
         logd("moveSatelliteToOffStateAndCleanUpResources");
         synchronized (mIsSatelliteEnabledLock) {
             resetSatelliteEnabledRequest();
@@ -2384,10 +3134,39 @@
         return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
     }
 
-    private void updateSupportedSatelliteServicesForActiveSubscriptions() {
+    private void configureSatellitePlmnForCarrier(int subId) {
+        logd("configureSatellitePlmnForCarrier");
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("configureSatellitePlmnForCarrier: carrierEnabledSatelliteFlag is disabled");
+            return;
+        }
         synchronized (mSupportedSatelliteServicesLock) {
-            mSupportedSatelliteServices.clear();
-            int[] activeSubIds = SubscriptionManagerService.getInstance().getActiveSubIdList(true);
+            List<String> carrierPlmnList = mMergedPlmnListPerCarrier.get(subId,
+                    new ArrayList<>()).stream().toList();
+            int slotId = SubscriptionManager.getSlotIndex(subId);
+            mSatelliteModemInterface.setSatellitePlmn(slotId, carrierPlmnList,
+                    SatelliteServiceUtils.mergeStrLists(
+                            carrierPlmnList, mSatellitePlmnListFromOverlayConfig),
+                    obtainMessage(EVENT_SET_SATELLITE_PLMN_INFO_DONE));
+        }
+    }
+
+    private void handleSetSatellitePlmnInfoDoneEvent(Message msg) {
+        AsyncResult ar = (AsyncResult) msg.obj;
+        SatelliteServiceUtils.getSatelliteError(ar, "handleSetSatellitePlmnInfoCmd");
+    }
+
+    private void updateSupportedSatelliteServicesForActiveSubscriptions() {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("updateSupportedSatelliteServicesForActiveSubscriptions: "
+                    + "carrierEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        synchronized (mSupportedSatelliteServicesLock) {
+            mSatelliteServicesSupportedByCarriers.clear();
+            mMergedPlmnListPerCarrier.clear();
+            int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(true);
             if (activeSubIds != null) {
                 for (int subId : activeSubIds) {
                     updateSupportedSatelliteServices(subId);
@@ -2399,22 +3178,50 @@
         }
     }
 
-    private void updateSupportedSatelliteServices(int subId) {
-        Map<String, Set<Integer>> carrierSupportedSatelliteServicesPerPlmn =
-                readSupportedSatelliteServicesFromCarrierConfig(subId);
+    /**
+     * If the entitlementPlmnList exist then used it.
+     * Otherwise, If the carrierPlmnList exist then used it.
+     */
+    private void updatePlmnListPerCarrier(int subId) {
         synchronized (mSupportedSatelliteServicesLock) {
-            mSupportedSatelliteServices.put(subId,
-                    SatelliteServiceUtils.mergeSupportedSatelliteServices(
-                            mSatelliteServicesSupportedByProviders,
-                            carrierSupportedSatelliteServicesPerPlmn));
+            List<String> carrierPlmnList, entitlementPlmnList;
+            entitlementPlmnList = mEntitlementPlmnListPerCarrier.get(subId,
+                    new ArrayList<>()).stream().toList();
+            if (!entitlementPlmnList.isEmpty()) {
+                mMergedPlmnListPerCarrier.put(subId, entitlementPlmnList);
+                logd("update it using entitlementPlmnList=" + entitlementPlmnList);
+                return;
+            }
+
+            if (mSatelliteServicesSupportedByCarriers.containsKey(subId)) {
+                carrierPlmnList =
+                        mSatelliteServicesSupportedByCarriers.get(subId).keySet().stream().toList();
+            } else {
+                carrierPlmnList = new ArrayList<>();
+            }
+            mMergedPlmnListPerCarrier.put(subId, carrierPlmnList);
+            logd("update it using carrierPlmnList=" + carrierPlmnList);
+        }
+    }
+
+    private void updateSupportedSatelliteServices(int subId) {
+        synchronized (mSupportedSatelliteServicesLock) {
+            mSatelliteServicesSupportedByCarriers.put(
+                    subId, readSupportedSatelliteServicesFromCarrierConfig(subId));
+            updatePlmnListPerCarrier(subId);
         }
     }
 
     @NonNull
-    private Map<String, Set<Integer>> readSupportedSatelliteServicesFromOverlayConfig() {
-        String[] supportedServices = readStringArrayFromOverlayConfig(
-                R.array.config_satellite_services_supported_by_providers);
-        return SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServices);
+    private List<String> readSatellitePlmnsFromOverlayConfig() {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("readSatellitePlmnsFromOverlayConfig: carrierEnabledSatelliteFlag is disabled");
+            return new ArrayList<>();
+        }
+
+        String[] devicePlmns = readStringArrayFromOverlayConfig(
+                R.array.config_satellite_providers);
+        return Arrays.stream(devicePlmns).toList();
     }
 
     @NonNull
@@ -2426,15 +3233,17 @@
                 mCarrierConfigArray.put(subId, config);
             }
             return SatelliteServiceUtils.parseSupportedSatelliteServices(
-                    config.getPersistableBundle(CarrierConfigManager
-                            .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE));
+                    config.getPersistableBundle(
+                            KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE));
         }
     }
 
     @NonNull private PersistableBundle getConfigForSubId(int subId) {
         PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId,
-                CarrierConfigManager
-                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE);
+                KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT,
+                KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL);
         if (config == null || config.isEmpty()) {
             config = CarrierConfigManager.getDefaultConfig();
         }
@@ -2451,15 +3260,68 @@
         }
 
         updateCarrierConfig(subId);
+        updateEntitlementPlmnListPerCarrier(subId);
         updateSupportedSatelliteServicesForActiveSubscriptions();
+        configureSatellitePlmnForCarrier(subId);
+
+        synchronized (mIsSatelliteEnabledLock) {
+            mSatelliteAttachRestrictionForCarrierArray.clear();
+            mIsSatelliteAttachEnabledForCarrierArrayPerSub.clear();
+        }
+
+        setSatelliteAttachEnabledForCarrierOnSimLoaded(subId);
+        updateRestrictReasonForEntitlementPerCarrier(subId);
     }
 
-    private void updateCarrierConfig(int subId) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void updateCarrierConfig(int subId) {
         synchronized (mCarrierConfigArrayLock) {
             mCarrierConfigArray.put(subId, getConfigForSubId(subId));
         }
     }
 
+    /** If there is no cached entitlement plmn list, read it from the db and use it if it is not an
+     * empty list. */
+    private void updateEntitlementPlmnListPerCarrier(int subId) {
+        if (!getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false)) {
+            logd("don't support entitlement");
+            return;
+        }
+
+        synchronized (mSupportedSatelliteServicesLock) {
+            if (mEntitlementPlmnListPerCarrier.indexOfKey(subId) < 0) {
+                logd("updateEntitlementPlmnListPerCarrier: no correspondent cache, load from "
+                        + "persist storage");
+                List<String> entitlementPlmnList =
+                        mSubscriptionManagerService.getSatelliteEntitlementPlmnList(subId);
+                if (entitlementPlmnList.isEmpty()) {
+                    loge("updateEntitlementPlmnListPerCarrier: no data for subId(" + subId + ")");
+                    return;
+                }
+                logd("updateEntitlementPlmnListPerCarrier: entitlementPlmnList="
+                        + entitlementPlmnList);
+                mEntitlementPlmnListPerCarrier.put(subId, entitlementPlmnList);
+            }
+        }
+    }
+
+    /**
+     * When a SIM is loaded, we need to check if users has enabled satellite attach for the carrier
+     * associated with the SIM, and evaluate if satellite should be enabled for the carrier.
+     *
+     * @param subId Subscription ID.
+     */
+    private void setSatelliteAttachEnabledForCarrierOnSimLoaded(int subId) {
+        synchronized (mIsSatelliteEnabledLock) {
+            if (isSatelliteAttachEnabledForCarrierByUser(subId)
+                    && !mIsSatelliteAttachEnabledForCarrierArrayPerSub.getOrDefault(subId,
+                    false)) {
+                evaluateEnablingSatelliteForCarrier(subId,
+                        SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, null);
+            }
+        }
+    }
+
     @NonNull
     private String[] readStringArrayFromOverlayConfig(@ArrayRes int id) {
         String[] strArray = null;
@@ -2474,10 +3336,624 @@
         return strArray;
     }
 
+    private boolean isSatelliteSupportedViaCarrier(int subId) {
+        return getConfigForSubId(subId)
+                .getBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL);
+    }
+
+    /**
+     * Check if satellite attach is enabled by user for the carrier associated with the
+     * {@code subId}.
+     *
+     * @param subId Subscription ID.
+     *
+     * @return Returns {@code true} if satellite attach for carrier is enabled by user,
+     * {@code false} otherwise.
+     */
+    private boolean isSatelliteAttachEnabledForCarrierByUser(int subId) {
+        synchronized (mIsSatelliteEnabledLock) {
+            Set<Integer> cachedRestrictionSet =
+                    mSatelliteAttachRestrictionForCarrierArray.get(subId);
+            if (cachedRestrictionSet != null) {
+                return !cachedRestrictionSet.contains(
+                        SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER);
+            } else {
+                logd("isSatelliteAttachEnabledForCarrierByUser() no correspondent cache, "
+                        + "load from persist storage");
+                try {
+                    String enabled =
+                            mSubscriptionManagerService.getSubscriptionProperty(subId,
+                                    SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
+                                    mContext.getOpPackageName(), mContext.getAttributionTag());
+
+                    if (enabled == null) {
+                        loge("isSatelliteAttachEnabledForCarrierByUser: invalid subId, subId="
+                                + subId);
+                        return false;
+                    }
+
+                    if (enabled.isEmpty()) {
+                        loge("isSatelliteAttachEnabledForCarrierByUser: no data for subId(" + subId
+                                + ")");
+                        return false;
+                    }
+
+                    synchronized (mIsSatelliteEnabledLock) {
+                        boolean result = enabled.equals("1");
+                        if (!result) {
+                            mSatelliteAttachRestrictionForCarrierArray.put(subId, new HashSet<>());
+                            mSatelliteAttachRestrictionForCarrierArray.get(subId).add(
+                                    SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER);
+                        }
+                        return result;
+                    }
+                } catch (IllegalArgumentException | SecurityException ex) {
+                    loge("isSatelliteAttachEnabledForCarrierByUser: ex=" + ex);
+                    return false;
+                }
+            }
+        }
+    }
+
+    /**
+     * Check whether there is any reason to restrict satellite communication for the carrier
+     * associated with the {@code subId}.
+     *
+     * @param subId Subscription ID
+     * @return {@code true} when there is at least on reason, {@code false} otherwise.
+     */
+    private boolean hasReasonToRestrictSatelliteCommunicationForCarrier(int subId) {
+        synchronized (mIsSatelliteEnabledLock) {
+            return !mSatelliteAttachRestrictionForCarrierArray
+                    .getOrDefault(subId, Collections.emptySet()).isEmpty();
+        }
+    }
+
+    private void updateRestrictReasonForEntitlementPerCarrier(int subId) {
+        if (!getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false)) {
+            logd("don't support entitlement");
+            return;
+        }
+
+        IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+            @Override
+            public void accept(int result) {
+                logd("updateRestrictReasonForEntitlementPerCarrier:" + result);
+            }
+        };
+        synchronized (mSupportedSatelliteServicesLock) {
+            if (mSatelliteEntitlementStatusPerCarrier.indexOfKey(subId) < 0) {
+                logd("updateRestrictReasonForEntitlementPerCarrier: no correspondent cache, "
+                        + "load from persist storage");
+                String entitlementStatus = null;
+                try {
+                    entitlementStatus =
+                            mSubscriptionManagerService.getSubscriptionProperty(subId,
+                                    SATELLITE_ENTITLEMENT_STATUS, mContext.getOpPackageName(),
+                                    mContext.getAttributionTag());
+                } catch (IllegalArgumentException | SecurityException e) {
+                    loge("updateRestrictReasonForEntitlementPerCarrier, e=" + e);
+                }
+
+                if (entitlementStatus == null) {
+                    loge("updateRestrictReasonForEntitlementPerCarrier: invalid subId, subId="
+                            + subId + " set to default value");
+                    entitlementStatus = "0";
+                }
+
+                if (entitlementStatus.isEmpty()) {
+                    loge("updateRestrictReasonForEntitlementPerCarrier: no data for subId(" + subId
+                            + "). set to default value");
+                    entitlementStatus = "0";
+                }
+                boolean result = entitlementStatus.equals("1");
+                mSatelliteEntitlementStatusPerCarrier.put(subId, result);
+            }
+
+            if (!mSatelliteEntitlementStatusPerCarrier.get(subId, false)) {
+                addAttachRestrictionForCarrier(subId,
+                        SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT, callback);
+            }
+        }
+    }
+
+    /**
+     * Save user setting for enabling satellite attach for the carrier associated with the
+     * {@code subId} to persistent storage.
+     *
+     * @param subId Subscription ID.
+     *
+     * @return {@code true} if persist successful, {@code false} otherwise.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean persistSatelliteAttachEnabledForCarrierSetting(int subId) {
+        logd("persistSatelliteAttachEnabledForCarrierSetting");
+        if (!isValidSubscriptionId(subId)) {
+            loge("persistSatelliteAttachEnabledForCarrierSetting: subId is not valid,"
+                    + " subId=" + subId);
+            return false;
+        }
+
+        synchronized (mIsSatelliteEnabledLock) {
+            try {
+                mSubscriptionManagerService.setSubscriptionProperty(subId,
+                        SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
+                        mSatelliteAttachRestrictionForCarrierArray.get(subId)
+                                .contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER)
+                                ? "0" : "1");
+            } catch (IllegalArgumentException | SecurityException ex) {
+                loge("persistSatelliteAttachEnabledForCarrierSetting, ex=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Evaluate whether satellite attach for carrier should be restricted.
+     *
+     * @param subId Subscription Id to evaluate for.
+     * @return {@code true} satellite attach is restricted, {@code false} otherwise.
+     */
+    private boolean isSatelliteRestrictedForCarrier(int subId) {
+        return !isSatelliteAttachEnabledForCarrierByUser(subId)
+                || hasReasonToRestrictSatelliteCommunicationForCarrier(subId);
+    }
+
+    /**
+     * Check whether satellite is enabled for carrier at modem.
+     *
+     * @param subId Subscription ID to check for.
+     * @return {@code true} if satellite modem is enabled, {@code false} otherwise.
+     */
+    private boolean isSatelliteEnabledForCarrierAtModem(int subId) {
+        synchronized (mIsSatelliteEnabledLock) {
+            return mIsSatelliteAttachEnabledForCarrierArrayPerSub.getOrDefault(subId, false);
+        }
+    }
+
+    /**
+     * Evaluate whether satellite modem for carrier should be enabled or not.
+     * <p>
+     * Satellite will be enabled only when the following conditions are met:
+     * <ul>
+     * <li>Users want to enable it.</li>
+     * <li>There is no satellite communication restriction, which is added by
+     * {@link #addAttachRestrictionForCarrier(int, int, IIntegerConsumer)}</li>
+     * <li>The carrier config {@link
+     * android.telephony.CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to
+     * {@code true}.</li>
+     * </ul>
+     *
+     * @param subId Subscription Id for evaluate for.
+     * @param callback The callback for getting the result of enabling satellite.
+     */
+    private void evaluateEnablingSatelliteForCarrier(int subId, int reason,
+            @Nullable Consumer<Integer> callback) {
+        if (callback == null) {
+            callback = errorCode -> logd("evaluateEnablingSatelliteForCarrier: "
+                    + "SetSatelliteAttachEnableForCarrier error code =" + errorCode);
+        }
+
+        if (!isSatelliteSupportedViaCarrier(subId)) {
+            logd("Satellite for carrier is not supported. Only user setting is stored");
+            callback.accept(SATELLITE_RESULT_SUCCESS);
+            return;
+        }
+
+        /* Request to enable or disable the satellite in the cellular modem only when the desired
+        state and the current state are different. */
+        boolean isSatelliteExpectedToBeEnabled = !isSatelliteRestrictedForCarrier(subId);
+        if (isSatelliteExpectedToBeEnabled != isSatelliteEnabledForCarrierAtModem(subId)) {
+            if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                int simSlot = SubscriptionManager.getSlotIndex(subId);
+                RequestHandleSatelliteAttachRestrictionForCarrierArgument argument =
+                        new RequestHandleSatelliteAttachRestrictionForCarrierArgument(subId,
+                                reason, callback);
+                SatelliteControllerHandlerRequest request =
+                        new SatelliteControllerHandlerRequest(argument,
+                                SatelliteServiceUtils.getPhone(subId));
+                Message onCompleted = obtainMessage(
+                        EVENT_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE_DONE, request);
+                mSatelliteModemInterface.requestSetSatelliteEnabledForCarrier(simSlot,
+                        isSatelliteExpectedToBeEnabled, onCompleted);
+            } else {
+                callback.accept(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
+            }
+        } else {
+            callback.accept(SATELLITE_RESULT_SUCCESS);
+        }
+    }
+
+    @SatelliteManager.SatelliteResult private int evaluateOemSatelliteRequestAllowed(
+            boolean isProvisionRequired) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("oemEnabledSatelliteFlag is disabled");
+            return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+        }
+        if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            logd("evaluateOemSatelliteRequestAllowed: satellite service is not supported");
+            return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+        }
+
+        Boolean satelliteSupported = isSatelliteSupportedViaOemInternal();
+        if (satelliteSupported == null) {
+            logd("evaluateOemSatelliteRequestAllowed: satelliteSupported is null");
+            return SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
+        }
+        if (!satelliteSupported) {
+            return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
+        }
+
+        if (isProvisionRequired) {
+            Boolean satelliteProvisioned = isSatelliteViaOemProvisioned();
+            if (satelliteProvisioned == null) {
+                logd("evaluateOemSatelliteRequestAllowed: satelliteProvisioned is null");
+                return SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
+            }
+            if (!satelliteProvisioned) {
+                return SatelliteManager.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED;
+            }
+        }
+
+        return SATELLITE_RESULT_SUCCESS;
+    }
+
+    /**
+     * Returns the non-terrestrial network radio technology that the satellite modem currently
+     * supports. If multiple technologies are available, returns the first supported technology.
+     */
+    @VisibleForTesting
+    protected @SatelliteManager.NTRadioTechnology int getSupportedNtnRadioTechnology() {
+        synchronized (mSatelliteCapabilitiesLock) {
+            if (mSatelliteCapabilities != null) {
+                return mSatelliteCapabilities.getSupportedRadioTechnologies()
+                        .stream().findFirst().orElse(SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN);
+            }
+            return SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN;
+        }
+    }
+
+    private void sendErrorAndReportSessionMetrics(@SatelliteManager.SatelliteResult int error,
+            Consumer<Integer> result) {
+        result.accept(error);
+        SessionMetricsStats.getInstance()
+                .setInitializationResult(error)
+                .setRadioTechnology(getSupportedNtnRadioTechnology())
+                .reportSessionMetrics();
+    }
+
+    private void registerForServiceStateChanged() {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            return;
+        }
+        for (Phone phone : PhoneFactory.getPhones()) {
+            phone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
+        }
+    }
+
+    private void handleEventServiceStateChanged() {
+        handleServiceStateForSatelliteConnectionViaCarrier();
+        determineSystemNotification();
+    }
+
+    private void handleServiceStateForSatelliteConnectionViaCarrier() {
+        for (Phone phone : PhoneFactory.getPhones()) {
+            ServiceState serviceState = phone.getServiceState();
+            if (serviceState != null) {
+                synchronized (mSatelliteConnectedLock) {
+                    if (serviceState.isUsingNonTerrestrialNetwork()) {
+                        mWasSatelliteConnectedViaCarrier.put(phone.getSubId(), true);
+                        mIsSatelliteConnectedViaCarrierHysteresisTimeExpired.put(
+                                phone.getSubId(), false);
+                    } else {
+                        Boolean connected = mWasSatelliteConnectedViaCarrier.get(phone.getSubId());
+                        if (connected != null && connected) {
+                            // The device just got disconnected from a satellite network.
+                            mLastSatelliteDisconnectedTimesMillis.put(
+                                    phone.getSubId(), getElapsedRealtime());
+                            mIsSatelliteConnectedViaCarrierHysteresisTimeExpired.put(
+                                    phone.getSubId(), false);
+                        }
+                        mWasSatelliteConnectedViaCarrier.put(phone.getSubId(), false);
+                    }
+                }
+            }
+        }
+    }
+
+    private long getSatelliteConnectionHysteresisTimeMillis(int subId) {
+        synchronized (mCarrierConfigArrayLock) {
+            PersistableBundle config = mCarrierConfigArray.get(subId);
+            if (config == null) {
+                config = getConfigForSubId(subId);
+                mCarrierConfigArray.put(subId, config);
+            }
+            return (config.getInt(
+                    KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT) * 1000L);
+        }
+    }
+
+    private void persistOemEnabledSatelliteProvisionStatus(boolean isProvisioned) {
+        synchronized (mSatelliteViaOemProvisionLock) {
+            logd("persistOemEnabledSatelliteProvisionStatus: isProvisioned=" + isProvisioned);
+
+            if (!loadSatelliteSharedPreferences()) return;
+
+            if (mSharedPreferences == null) {
+                loge("persistOemEnabledSatelliteProvisionStatus: mSharedPreferences is null");
+            } else {
+                mSharedPreferences.edit().putBoolean(
+                        OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY, isProvisioned).apply();
+            }
+        }
+    }
+
+    private boolean getPersistedOemEnabledSatelliteProvisionStatus() {
+        synchronized (mSatelliteViaOemProvisionLock) {
+            if (!loadSatelliteSharedPreferences()) return false;
+
+            if (mSharedPreferences == null) {
+                loge("getPersistedOemEnabledSatelliteProvisionStatus: mSharedPreferences is null");
+                return false;
+            } else {
+                return mSharedPreferences.getBoolean(
+                        OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY, false);
+            }
+        }
+    }
+
+    private boolean loadSatelliteSharedPreferences() {
+        if (mSharedPreferences == null) {
+            try {
+                mSharedPreferences =
+                        mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
+                                Context.MODE_PRIVATE);
+            } catch (Exception e) {
+                loge("loadSatelliteSharedPreferences: Cannot get default "
+                        + "shared preferences, e=" + e);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void handleIsSatelliteProvisionedDoneEvent(@NonNull AsyncResult ar) {
+        SatelliteControllerHandlerRequest request = (SatelliteControllerHandlerRequest) ar.userObj;
+        int error = SatelliteServiceUtils.getSatelliteError(
+                ar, "handleIsSatelliteProvisionedDoneEvent");
+        boolean isSatelliteProvisionedInModem = false;
+        if (error == SATELLITE_RESULT_SUCCESS) {
+            if (ar.result == null) {
+                loge("handleIsSatelliteProvisionedDoneEvent: result is null");
+                error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
+            } else {
+                isSatelliteProvisionedInModem = ((int[]) ar.result)[0] == 1;
+            }
+        } else if (error == SATELLITE_RESULT_REQUEST_NOT_SUPPORTED) {
+            logd("handleIsSatelliteProvisionedDoneEvent: Modem does not support this request");
+            isSatelliteProvisionedInModem = true;
+        }
+        boolean isSatelliteViaOemProvisioned =
+                isSatelliteProvisionedInModem && getPersistedOemEnabledSatelliteProvisionStatus();
+        logd("isSatelliteProvisionedInModem=" + isSatelliteProvisionedInModem
+                + ", isSatelliteViaOemProvisioned=" + isSatelliteViaOemProvisioned);
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED, isSatelliteViaOemProvisioned);
+        synchronized (mSatelliteViaOemProvisionLock) {
+            mIsSatelliteViaOemProvisioned = isSatelliteViaOemProvisioned;
+        }
+        ((ResultReceiver) request.argument).send(error, bundle);
+    }
+
+    private long getWaitForSatelliteEnablingResponseTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_wait_for_satellite_enabling_response_timeout_millis);
+    }
+
+    private void startWaitForSatelliteEnablingResponseTimer(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (hasMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument)) {
+                logd("WaitForSatelliteEnablingResponseTimer of request ID "
+                        + argument.requestId + " was already started");
+                return;
+            }
+            logd("Start timer to wait for response of the satellite enabling request ID="
+                    + argument.requestId + ", enableSatellite=" + argument.enableSatellite
+                    + ", mWaitTimeForSatelliteEnablingResponse="
+                    + mWaitTimeForSatelliteEnablingResponse);
+            sendMessageDelayed(obtainMessage(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT,
+                            argument), mWaitTimeForSatelliteEnablingResponse);
+        }
+    }
+
+    private void stopWaitForSatelliteEnablingResponseTimer(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            logd("Stop timer to wait for response of the satellite enabling request ID="
+                    + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
+            removeMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument);
+        }
+    }
+
+    private boolean shouldProcessEventSetSatelliteEnabledDone(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (hasMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private void handleEventWaitForSatelliteEnablingResponseTimedOut(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        logw("Timed out to wait for response of the satellite enabling request ID="
+                + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
+
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (mSatelliteEnabledRequest != null) {
+                if (mSatelliteEnabledRequest.enableSatellite && !argument.enableSatellite
+                        && mWaitingForRadioDisabled) {
+                    // Previous mSatelliteEnabledRequest is successful but waiting for
+                    // all radios to be turned off.
+                    mSatelliteEnabledRequest.callback.accept(SATELLITE_RESULT_SUCCESS);
+                    resetSatelliteEnabledRequest();
+                } else if (mSatelliteEnabledRequest.requestId == argument.requestId) {
+                    resetSatelliteEnabledRequest();
+                }
+            }
+        }
+        argument.callback.accept(SATELLITE_RESULT_MODEM_TIMEOUT);
+
+        synchronized (mIsSatelliteEnabledLock) {
+            if (argument.enableSatellite) {
+                if (!mWaitingForDisableSatelliteModemResponse && !mWaitingForSatelliteModemOff) {
+                    resetSatelliteEnabledRequest();
+                    IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            logd("handleEventWaitForSatelliteEnablingResponseTimedOut: "
+                                    + "disable satellite result=" + result);
+                        }
+                    };
+                    Consumer<Integer> result =
+                            FunctionalUtils.ignoreRemoteException(callback::accept);
+                    RequestSatelliteEnabledArgument request = new RequestSatelliteEnabledArgument(
+                            false, false, result);
+                    synchronized (mSatelliteEnabledRequestLock) {
+                        mSatelliteEnabledRequest = request;
+                    }
+                    sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
+                }
+                mControllerMetricsStats.reportServiceEnablementFailCount();
+                SessionMetricsStats.getInstance()
+                        .setInitializationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
+                        .setRadioTechnology(getSupportedNtnRadioTechnology())
+                        .reportSessionMetrics();
+            } else {
+                /*
+                 * Unregister Importance Listener for Pointing UI when Satellite is disabled
+                 */
+                synchronized (mNeedsSatellitePointingLock) {
+                    if (mNeedsSatellitePointing) {
+                        mPointingAppController.removeListenerForPointingUI();
+                    }
+                }
+                moveSatelliteToOffStateAndCleanUpResources(SATELLITE_RESULT_MODEM_TIMEOUT, null);
+                mControllerMetricsStats.onSatelliteDisabled();
+                mWaitingForDisableSatelliteModemResponse = false;
+                mWaitingForSatelliteModemOff = false;
+            }
+        }
+    }
+
+    /**
+     * This API can be used by only CTS to override the cached value for the device overlay config
+     * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
+     * outgoing satellite datagrams should be sent to modem in demo mode.
+     *
+     * @param shouldSendToModemInDemoMode Whether send datagram in demo mode should be sent to
+     * satellite modem or not.
+     *
+     * @return {@code true} if the operation is successful, {@code false} otherwise.
+     */
+    public boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setShouldSendDatagramToModemInDemoMode: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+
+        if (!isMockModemAllowed()) {
+            logd("setShouldSendDatagramToModemInDemoMode: mock modem not allowed.");
+            return false;
+        }
+
+        mDatagramController.setShouldSendDatagramToModemInDemoMode(shouldSendToModemInDemoMode);
+        return true;
+    }
+
+    private void determineSystemNotification() {
+        if (isUsingNonTerrestrialNetworkViaCarrier()) {
+            if (mSharedPreferences == null) {
+                try {
+                    mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
+                            Context.MODE_PRIVATE);
+                } catch (Exception e) {
+                    loge("Cannot get default shared preferences: " + e);
+                }
+            }
+            if (mSharedPreferences == null) {
+                loge("handleEventServiceStateChanged: Cannot get default shared preferences");
+                return;
+            }
+            if (!mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false)) {
+                showSatelliteSystemNotification();
+                mSharedPreferences.edit().putBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY,
+                        true).apply();
+            }
+        }
+    }
+
+    private void showSatelliteSystemNotification() {
+        logd("showSatelliteSystemNotification");
+        final NotificationChannel notificationChannel = new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL,
+                NotificationManager.IMPORTANCE_DEFAULT
+        );
+        notificationChannel.setSound(null, null);
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        notificationManager.createNotificationChannel(notificationChannel);
+
+        Notification.Builder notificationBuilder = new Notification.Builder(mContext)
+                .setContentTitle(mContext.getResources().getString(
+                        R.string.satellite_notification_title))
+                .setContentText(mContext.getResources().getString(
+                        R.string.satellite_notification_summary))
+                .setSmallIcon(R.drawable.ic_satellite_alt_24px)
+                .setChannelId(NOTIFICATION_CHANNEL_ID)
+                .setAutoCancel(true)
+                .setColor(mContext.getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setVisibility(Notification.VISIBILITY_PUBLIC);
+
+        // Add action to invoke `What to expect` dialog of Messaging application.
+        Intent intentOpenMessage = new Intent(Intent.ACTION_VIEW);
+        intentOpenMessage.setData(Uri.parse("sms:"));
+        // TODO : b/322733285 add putExtra to invoke "What to expect" dialog.
+        PendingIntent pendingIntentOpenMessage = PendingIntent.getActivity(mContext, 0,
+                intentOpenMessage, PendingIntent.FLAG_IMMUTABLE);
+
+        Notification.Action actionOpenMessage = new Notification.Action.Builder(0,
+                mContext.getResources().getString(R.string.satellite_notification_open_message),
+                pendingIntentOpenMessage).build();
+        notificationBuilder.addAction(actionOpenMessage);
+
+        // Add action to invoke Satellite setting activity in Settings.
+        Intent intentSatelliteSetting = new Intent(ACTION_SATELLITE_SETTING);
+        PendingIntent pendingIntentSatelliteSetting = PendingIntent.getActivity(mContext, 0,
+                intentSatelliteSetting, PendingIntent.FLAG_IMMUTABLE);
+
+        Notification.Action actionOpenSatelliteSetting = new Notification.Action.Builder(null,
+                mContext.getResources().getString(R.string.satellite_notification_how_it_works),
+                pendingIntentSatelliteSetting).build();
+        notificationBuilder.addAction(actionOpenSatelliteSetting);
+
+        notificationManager.notifyAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
+                notificationBuilder.build(), UserHandle.ALL);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
 
+    private static void logw(@NonNull String log) {
+        Rlog.w(TAG, log);
+    }
+
     private static void loge(@NonNull String log) {
         Rlog.e(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
index ebf7780..2f86eea 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -30,11 +30,15 @@
 import android.os.Message;
 import android.os.RegistrantList;
 import android.os.RemoteException;
+import android.telephony.IBooleanConsumer;
+import android.telephony.IIntegerConsumer;
 import android.telephony.Rlog;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteManager.SatelliteException;
+import android.telephony.satellite.stub.INtnSignalStrengthConsumer;
 import android.telephony.satellite.stub.ISatellite;
 import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
 import android.telephony.satellite.stub.ISatelliteListener;
@@ -45,10 +49,9 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.ExponentialBackoff;
-import com.android.internal.telephony.IBooleanConsumer;
-import com.android.internal.telephony.IIntegerConsumer;
 
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * Satellite modem interface to manage connections with the satellite service and HAL interface.
@@ -86,6 +89,10 @@
     @NonNull private final RegistrantList mPendingDatagramsRegistrants = new RegistrantList();
     @NonNull private final RegistrantList mSatelliteDatagramsReceivedRegistrants =
             new RegistrantList();
+    @NonNull private final RegistrantList mNtnSignalStrengthChangedRegistrants =
+            new RegistrantList();
+    @NonNull private final RegistrantList mSatelliteCapabilitiesChangedRegistrants =
+            new RegistrantList();
 
     @NonNull private final ISatelliteListener mListener = new ISatelliteListener.Stub() {
         @Override
@@ -96,12 +103,14 @@
         @Override
         public void onSatelliteDatagramReceived(
                 android.telephony.satellite.stub.SatelliteDatagram datagram, int pendingCount) {
+            logd("onSatelliteDatagramReceived: pendingCount=" + pendingCount);
             mSatelliteDatagramsReceivedRegistrants.notifyResult(new Pair<>(
                     SatelliteServiceUtils.fromSatelliteDatagram(datagram), pendingCount));
         }
 
         @Override
         public void onPendingDatagrams() {
+            logd("onPendingDatagrams");
             mPendingDatagramsRegistrants.notifyResult(null);
         }
 
@@ -135,6 +144,20 @@
             }
             mDatagramTransferStateChangedRegistrants.notifyResult(datagramTransferState);
         }
+
+        @Override
+        public void onNtnSignalStrengthChanged(
+                android.telephony.satellite.stub.NtnSignalStrength ntnSignalStrength) {
+            mNtnSignalStrengthChangedRegistrants.notifyResult(
+                    SatelliteServiceUtils.fromNtnSignalStrength(ntnSignalStrength));
+        }
+
+        @Override
+        public void onSatelliteCapabilitiesChanged(
+                android.telephony.satellite.stub.SatelliteCapabilities satelliteCapabilities) {
+            mSatelliteCapabilitiesChangedRegistrants.notifyResult(
+                    SatelliteServiceUtils.fromSatelliteCapabilities(satelliteCapabilities));
+        }
     };
 
     /**
@@ -440,6 +463,48 @@
     }
 
     /**
+     * Registers for non-terrestrial signal strength level changed.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForNtnSignalStrengthChanged(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mNtnSignalStrengthChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for non-terrestrial signal strength level changed.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForNtnSignalStrengthChanged(@NonNull Handler h) {
+        mNtnSignalStrengthChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Registers for satellite capabilities changed.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteCapabilitiesChanged(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mSatelliteCapabilitiesChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for satellite capabilities changed.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatelliteCapabilitiesChanged(@NonNull Handler h) {
+        mSatelliteCapabilitiesChangedRegistrants.remove(h);
+    }
+
+    /**
      * Request to enable or disable the satellite service listening mode.
      * Listening mode allows the satellite service to listen for incoming pages.
      *
@@ -469,14 +534,14 @@
                 loge("requestSatelliteListeningEnabled: RemoteException " + e);
                 if (message != null) {
                     sendMessageWithResult(
-                            message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                            message, null, SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
                 }
             }
         } else {
             loge("requestSatelliteListeningEnabled: Satellite service is unavailable.");
             if (message != null) {
                 sendMessageWithResult(message, null,
-                        SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+                        SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
             }
         }
     }
@@ -508,14 +573,14 @@
                 loge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
                 if (message != null) {
                     sendMessageWithResult(
-                            message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                            message, null, SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
                 }
             }
         } else {
             loge("enableCellularModemWhileSatelliteModeIsOn: Satellite service is unavailable.");
             if (message != null) {
                 sendMessageWithResult(message, null,
-                        SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+                        SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
             }
         }
     }
@@ -544,11 +609,13 @@
                 });
             } catch (RemoteException e) {
                 loge("setSatelliteEnabled: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("setSatelliteEnabled: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -576,16 +643,18 @@
                         int[] enabled = new int[] {result ? 1 : 0};
                         logd("requestIsSatelliteEnabled: " + Arrays.toString(enabled));
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                message, enabled, SatelliteManager.SATELLITE_ERROR_NONE));
+                                message, enabled, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
                 loge("requestIsSatelliteEnabled: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("requestIsSatelliteEnabled: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -610,17 +679,18 @@
                     public void accept(boolean result) {
                         logd("requestIsSatelliteSupported: " + result);
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                message, result, SatelliteManager.SATELLITE_ERROR_NONE));
+                                message, result, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
                 loge("requestIsSatelliteSupported: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("requestIsSatelliteSupported: Satellite service is unavailable.");
             sendMessageWithResult(
-                    message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+                    message, null, SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -648,16 +718,18 @@
                                 SatelliteServiceUtils.fromSatelliteCapabilities(result);
                         logd("requestSatelliteCapabilities: " + capabilities);
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                message, capabilities, SatelliteManager.SATELLITE_ERROR_NONE));
+                                message, capabilities, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
                 loge("requestSatelliteCapabilities: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("requestSatelliteCapabilities: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -682,11 +754,13 @@
                 });
             } catch (RemoteException e) {
                 loge("startSendingSatellitePointingInfo: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("startSendingSatellitePointingInfo: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -710,11 +784,13 @@
                 });
             } catch (RemoteException e) {
                 loge("stopSendingSatellitePointingInfo: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("stopSendingSatellitePointingInfo: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -744,11 +820,13 @@
                         });
             } catch (RemoteException e) {
                 loge("provisionSatelliteService: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("provisionSatelliteService: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -774,11 +852,13 @@
                 });
             } catch (RemoteException e) {
                 loge("deprovisionSatelliteService: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("deprovisionSatelliteService: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -806,16 +886,18 @@
                         int[] provisioned = new int[] {result ? 1 : 0};
                         logd("requestIsSatelliteProvisioned: " + Arrays.toString(provisioned));
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                message, provisioned, SatelliteManager.SATELLITE_ERROR_NONE));
+                                message, provisioned, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
                 loge("requestIsSatelliteProvisioned: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("requestIsSatelliteProvisioned: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -833,18 +915,20 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("pollPendingSatelliteDatagrams: " + error);
+                        logd("pollPendingDatagrams: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 });
             } catch (RemoteException e) {
-                loge("pollPendingSatelliteDatagrams: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                loge("pollPendingDatagrams: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("pollPendingSatelliteDatagrams: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            loge("pollPendingDatagrams: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -867,18 +951,20 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("sendSatelliteDatagram: " + error);
+                                logd("sendDatagram: " + error);
                                 Binder.withCleanCallingIdentity(() ->
                                         sendMessageWithResult(message, null, error));
                             }
                         });
             } catch (RemoteException e) {
-                loge("sendSatelliteDatagram: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                loge("sendDatagram: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("sendSatelliteDatagram: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            loge("sendDatagram: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -907,16 +993,18 @@
                         int modemState = SatelliteServiceUtils.fromSatelliteModemState(result);
                         logd("requestSatelliteModemState: " + modemState);
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                message, modemState, SatelliteManager.SATELLITE_ERROR_NONE));
+                                message, modemState, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
                 loge("requestSatelliteModemState: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("requestSatelliteModemState: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -933,7 +1021,7 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: "
+                                logd("requestIsCommunicationAllowedForCurrentLocation: "
                                         + error);
                                 Binder.withCleanCallingIdentity(() ->
                                         sendMessageWithResult(message, null, error));
@@ -941,21 +1029,24 @@
                         }, new IBooleanConsumer.Stub() {
                             @Override
                             public void accept(boolean result) {
-                                logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: "
+                                logd("requestIsCommunicationAllowedForCurrentLocation: "
                                         + result);
                                 Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                        message, result, SatelliteManager.SATELLITE_ERROR_NONE));
+                                        message, result,
+                                        SatelliteManager.SATELLITE_RESULT_SUCCESS));
                             }
                         });
             } catch (RemoteException e) {
-                loge("requestIsSatelliteCommunicationAllowedForCurrentLocation: RemoteException "
+                loge("requestIsCommunicationAllowedForCurrentLocation: RemoteException "
                         + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestIsSatelliteCommunicationAllowedForCurrentLocation: "
+            loge("requestIsCommunicationAllowedForCurrentLocation: "
                     + "Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -987,16 +1078,271 @@
                                 logd("requestTimeForNextSatelliteVisibility: "
                                         + Arrays.toString(time));
                                 Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                        message, time, SatelliteManager.SATELLITE_ERROR_NONE));
+                                        message, time, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                             }
                         });
             } catch (RemoteException e) {
                 loge("requestTimeForNextSatelliteVisibility: RemoteException " + e);
-                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
             loge("requestTimeForNextSatelliteVisibility: Satellite service is unavailable.");
-            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Set the non-terrestrial PLMN with lower priority than terrestrial networks.
+     * MCC/MNC broadcast by the non-terrestrial networks will not be included in OPLMNwACT file
+     * on SIM profile.
+     * Acquisition of satellite based system is deemed lower priority to terrestrial networks.
+     * Even so, UE shall make all attempts to acquire terrestrial service prior to camping on
+     * satellite LTE service.
+     *
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+     *                        supported by user subscription.
+     * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+     *                             PLMNs that are not supported by the carrier and make sure not to
+     *                             attach to them.
+     * @param message The result receiver that returns whether the modem has
+     *                successfully set the satellite PLMN
+     */
+    public void setSatellitePlmn(@NonNull int simSlot, @NonNull List<String> carrierPlmnList,
+            @NonNull List<String> allSatellitePlmnList, @NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.setSatellitePlmn(simSlot, carrierPlmnList, allSatellitePlmnList,
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("setSatellitePlmn: " + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("setSatellitePlmn: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            loge("setSatellitePlmn: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Enable or disable satellite in the cellular modem associated with a carrier.
+     * Refer setSatellitePlmn for the details of satellite PLMN scanning process.
+     *
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestSetSatelliteEnabledForCarrier(@NonNull int simSlot,
+            @NonNull boolean enableSatellite, @NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.setSatelliteEnabledForCarrier(simSlot, enableSatellite,
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("requestSetSatelliteEnabledForCarrier: " + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("requestSetSatelliteEnabledForCarrier: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestSetSatelliteEnabledForCarrier: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+        }
+    }
+
+    /**
+     * Check whether satellite is enabled in the cellular modem associated with a carrier.
+     *
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestIsSatelliteEnabledForCarrier(@NonNull int simSlot,
+            @NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestIsSatelliteEnabledForCarrier(simSlot,
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("requestIsSatelliteEnabledForCarrier: " + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        }, new IBooleanConsumer.Stub() {
+                            @Override
+                            public void accept(boolean result) {
+                                // Convert for compatibility with SatelliteResponse
+                                // TODO: This should just report result instead.
+                                int[] enabled = new int[] {result ? 1 : 0};
+                                logd("requestIsSatelliteEnabledForCarrier: "
+                                        + Arrays.toString(enabled));
+                                Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
+                                        message, enabled,
+                                        SatelliteManager.SATELLITE_RESULT_SUCCESS));
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("requestIsSatelliteEnabledForCarrier: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestIsSatelliteEnabledForCarrier: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestNtnSignalStrength(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestSignalStrength(
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("requestNtnSignalStrength: " + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        }, new INtnSignalStrengthConsumer.Stub() {
+                            @Override
+                            public void accept(
+                                    android.telephony.satellite.stub.NtnSignalStrength result) {
+                                NtnSignalStrength ntnSignalStrength =
+                                        SatelliteServiceUtils.fromNtnSignalStrength(result);
+                                logd("requestNtnSignalStrength: " + ntnSignalStrength);
+                                Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
+                                        message, ntnSignalStrength,
+                                        SatelliteManager.SATELLITE_RESULT_SUCCESS));
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("requestNtnSignalStrength: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestNtnSignalStrength: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * The satellite service should report the NTN signal strength via
+     * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void startSendingNtnSignalStrength(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.startSendingNtnSignalStrength(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("startSendingNtnSignalStrength: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("startSendingNtnSignalStrength: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            loge("startSendingNtnSignalStrength: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * The satellite service should stop reporting NTN signal strength to the framework.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void stopSendingNtnSignalStrength(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.stopSendingNtnSignalStrength(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("stopSendingNtnSignalStrength: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("stopSendingNtnSignalStrength: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            loge("stopSendingNtnSignalStrength: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * The satellite service should abort all datagram-sending requests.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void abortSendingSatelliteDatagrams(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.abortSendingSatelliteDatagrams(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("abortSendingSatelliteDatagrams: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("abortSendingSatelliteDatagrams: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            loge("abortSendingSatelliteDatagrams: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
     }
 
@@ -1004,6 +1350,13 @@
         return mIsSatelliteServiceSupported;
     }
 
+    /** Check if vendor satellite service is connected */
+    public boolean isSatelliteServiceConnected() {
+        synchronized (mLock) {
+            return (mSatelliteService != null);
+        }
+    }
+
     /**
      * This API can be used by only CTS to update satellite vendor service package name.
      *
@@ -1036,8 +1389,8 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected static void sendMessageWithResult(@NonNull Message message, @Nullable Object result,
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = error == SatelliteManager.SATELLITE_ERROR_NONE
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = error == SatelliteManager.SATELLITE_RESULT_SUCCESS
                 ? null : new SatelliteException(error);
         AsyncResult.forMessage(message, result, exception);
         message.sendToTarget();
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteNetworkInfo.java b/src/java/com/android/internal/telephony/satellite/SatelliteNetworkInfo.java
new file mode 100644
index 0000000..7db9195
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteNetworkInfo.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Data class of the satellite configuration received from the entitlement server.
+ */
+public class SatelliteNetworkInfo {
+    /** Stored the allowed plmn for using the satellite service. */
+    public String mPlmn;
+    /** Stored the DataPlanType. It is an optional value that can be one of the following three
+     * values.
+     * 1. "unmetered"
+     * 2. "metered"
+     * 3. empty string. */
+    public String mDataPlanType;
+
+    public SatelliteNetworkInfo(String plmn, String dataPlanType) {
+        mPlmn = plmn;
+        mDataPlanType = dataPlanType;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
index 25657b3..149b054 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
@@ -16,36 +16,55 @@
 
 package com.android.internal.telephony.satellite;
 
-import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_ERROR_NONE;
+import static android.telephony.ServiceState.STATE_EMERGENCY_ONLY;
+import static android.telephony.ServiceState.STATE_IN_SERVICE;
+import static android.telephony.ServiceState.STATE_OUT_OF_SERVICE;
+import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
+import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT;
+import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
+import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
+
+import static com.android.internal.telephony.satellite.SatelliteController.INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
 
 import android.annotation.NonNull;
-import android.os.AsyncResult;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.ResultReceiver;
+import android.os.OutcomeReceiver;
+import android.os.SystemProperties;
 import android.provider.DeviceConfig;
-import android.telecom.Call;
 import android.telecom.Connection;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsRegistrationAttributes;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
+import android.telephony.satellite.SatelliteManager;
+import android.text.TextUtils;
 import android.util.Pair;
+import android.util.SparseArray;
 
 import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SmsApplication;
 import com.android.internal.telephony.metrics.SatelliteStats;
 
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
 
 
 /**
@@ -56,74 +75,54 @@
  */
 public class SatelliteSOSMessageRecommender extends Handler {
     private static final String TAG = "SatelliteSOSMessageRecommender";
-
-    /**
-     * Device config for the timeout duration in milliseconds to determine whether to recommend
-     * Dialer to show the SOS button to users.
-     * <p>
-     * The timer is started when there is an ongoing emergency call, and the IMS is not registered,
-     * and cellular service is not available. When the timer expires, SatelliteSOSMessageRecommender
-     * will send the event EVENT_DISPLAY_SOS_MESSAGE to Dialer, which will then prompt user to
-     * switch to using satellite SOS messaging.
-     */
-    public static final String EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS =
-            "emergency_call_to_sos_msg_hysteresis_timeout_millis";
-    /**
-     * The default value of {@link #EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS} when it is
-     * not provided in the device config.
-     */
-    public static final long DEFAULT_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = 20000;
-
+    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+    private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem";
     private static final int EVENT_EMERGENCY_CALL_STARTED = 1;
-    protected static final int EVENT_CELLULAR_SERVICE_STATE_CHANGED = 2;
-    private static final int EVENT_IMS_REGISTRATION_STATE_CHANGED = 3;
-    protected static final int EVENT_TIME_OUT = 4;
-    private static final int EVENT_SATELLITE_PROVISIONED_STATE_CHANGED = 5;
-    private static final int EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED = 6;
+    protected static final int EVENT_SERVICE_STATE_CHANGED = 2;
+    protected static final int EVENT_TIME_OUT = 3;
+    private static final int EVENT_SATELLITE_PROVISIONED_STATE_CHANGED = 4;
+    private static final int EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED = 5;
+    private static final int CMD_SEND_EVENT_DISPLAY_EMERGENCY_MESSAGE_FORCEFULLY = 6;
+    private static final int EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT = 7;
 
+    @NonNull private final Context mContext;
     @NonNull
     private final SatelliteController mSatelliteController;
     private ImsManager mImsManager;
 
     private Connection mEmergencyConnection = null;
-    /* The phone used for emergency call */
-    private Phone mPhone = null;
     private final ISatelliteProvisionStateCallback mISatelliteProvisionStateCallback;
-    @ServiceState.RegState
-    private AtomicInteger mCellularServiceState = new AtomicInteger();
-    private AtomicBoolean mIsImsRegistered = new AtomicBoolean();
-    private AtomicBoolean mIsSatelliteAllowedInCurrentLocation = new AtomicBoolean();
-    private final ResultReceiver mReceiverForRequestIsSatelliteAllowedForCurrentLocation;
+    /** Key: Phone ID; Value: IMS RegistrationCallback */
+    private SparseArray<RegistrationManager.RegistrationCallback>
+            mImsRegistrationCallbacks = new SparseArray<>();
+    @GuardedBy("mLock")
+    private boolean mIsSatelliteAllowedForCurrentLocation = false;
+    @GuardedBy("mLock")
+    private boolean mCheckingAccessRestrictionInProgress = false;
     private final long mTimeoutMillis;
+    private final AtomicBoolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime =
+            new AtomicBoolean(false);
+    @GuardedBy("mLock")
+    private boolean mIsTimerTimedOut = false;
     protected int mCountOfTimerStarted = 0;
-
-    private RegistrationManager.RegistrationCallback mImsRegistrationCallback =
-            new RegistrationManager.RegistrationCallback() {
-                @Override
-                public void onRegistered(ImsRegistrationAttributes attributes) {
-                    sendMessage(obtainMessage(EVENT_IMS_REGISTRATION_STATE_CHANGED, true));
-                }
-
-                @Override
-                public void onUnregistered(ImsReasonInfo info) {
-                    sendMessage(obtainMessage(EVENT_IMS_REGISTRATION_STATE_CHANGED, false));
-                }
-            };
+    private final Object mLock = new Object();
 
     /**
      * Create an instance of SatelliteSOSMessageRecommender.
      *
+     * @param context The Context for the SatelliteSOSMessageRecommender.
      * @param looper The looper used with the handler of this class.
      */
-    public SatelliteSOSMessageRecommender(@NonNull Looper looper) {
-        this(looper, SatelliteController.getInstance(), null,
-                getEmergencyCallToSosMsgHysteresisTimeoutMillis());
+    public SatelliteSOSMessageRecommender(@NonNull Context context, @NonNull Looper looper) {
+        this(context, looper, SatelliteController.getInstance(), null,
+                getEmergencyCallWaitForConnectionTimeoutMillis(context));
     }
 
     /**
      * Create an instance of SatelliteSOSMessageRecommender. This constructor should be used in
      * only unit tests.
      *
+     * @param context The Context for the SatelliteSOSMessageRecommender.
      * @param looper The looper used with the handler of this class.
      * @param satelliteController The SatelliteController singleton instance.
      * @param imsManager The ImsManager instance associated with the phone, which is used for making
@@ -131,10 +130,11 @@
      * @param timeoutMillis The timeout duration of the timer.
      */
     @VisibleForTesting
-    protected SatelliteSOSMessageRecommender(@NonNull Looper looper,
+    protected SatelliteSOSMessageRecommender(@NonNull Context context, @NonNull Looper looper,
             @NonNull SatelliteController satelliteController, ImsManager imsManager,
             long timeoutMillis) {
         super(looper);
+        mContext = context;
         mSatelliteController = satelliteController;
         mImsManager = imsManager;
         mTimeoutMillis = timeoutMillis;
@@ -145,38 +145,13 @@
                 sendMessage(obtainMessage(EVENT_SATELLITE_PROVISIONED_STATE_CHANGED, provisioned));
             }
         };
-        mReceiverForRequestIsSatelliteAllowedForCurrentLocation = new ResultReceiver(this) {
-            @Override
-            protected void onReceiveResult(int resultCode, Bundle resultData) {
-                if (resultCode == SATELLITE_ERROR_NONE) {
-                    if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
-                        boolean isSatelliteCommunicationAllowed =
-                                resultData.getBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED);
-                        mIsSatelliteAllowedInCurrentLocation.set(isSatelliteCommunicationAllowed);
-                        if (!isSatelliteCommunicationAllowed) {
-                            logd("Satellite is not allowed for current location.");
-                            cleanUpResources();
-                        }
-                    } else {
-                        loge("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
-                        mIsSatelliteAllowedInCurrentLocation.set(false);
-                        cleanUpResources();
-                    }
-                } else {
-                    loge("requestIsSatelliteCommunicationAllowedForCurrentLocation() resultCode="
-                            + resultCode);
-                    mIsSatelliteAllowedInCurrentLocation.set(false);
-                    cleanUpResources();
-                }
-            }
-        };
     }
 
     @Override
     public void handleMessage(@NonNull Message msg) {
         switch (msg.what) {
             case EVENT_EMERGENCY_CALL_STARTED:
-                handleEmergencyCallStartedEvent((Pair<Connection, Phone>) msg.obj);
+                handleEmergencyCallStartedEvent((Connection) msg.obj);
                 break;
             case EVENT_TIME_OUT:
                 handleTimeoutEvent();
@@ -187,12 +162,14 @@
             case EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED:
                 handleEmergencyCallConnectionStateChangedEvent((Pair<String, Integer>) msg.obj);
                 break;
-            case EVENT_IMS_REGISTRATION_STATE_CHANGED:
-                handleImsRegistrationStateChangedEvent((boolean) msg.obj);
+            case EVENT_SERVICE_STATE_CHANGED:
+                handleStateChangedEventForHysteresisTimer();
                 break;
-            case EVENT_CELLULAR_SERVICE_STATE_CHANGED:
-                AsyncResult ar = (AsyncResult) msg.obj;
-                handleCellularServiceStateChangedEvent((ServiceState) ar.result);
+            case CMD_SEND_EVENT_DISPLAY_EMERGENCY_MESSAGE_FORCEFULLY:
+                handleCmdSendEventDisplayEmergencyMessageForcefully((Connection) msg.obj);
+                break;
+            case EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT:
+                handleSatelliteAccessRestrictionCheckingResult((boolean) msg.obj);
                 break;
             default:
                 logd("handleMessage: unexpected message code: " + msg.what);
@@ -205,15 +182,23 @@
      *
      * @param connection The connection created by TelephonyConnectionService for the emergency
      *                   call.
-     * @param phone The phone used for the emergency call.
      */
-    public void onEmergencyCallStarted(@NonNull Connection connection, @NonNull Phone phone) {
-        if (!mSatelliteController.isSatelliteSupported()) {
+    public void onEmergencyCallStarted(@NonNull Connection connection) {
+        if (!mSatelliteController.isSatelliteSupportedViaOem()
+                && !mSatelliteController.isSatelliteSupportedViaCarrier()) {
             logd("onEmergencyCallStarted: satellite is not supported");
             return;
         }
-        Pair<Connection, Phone> argument = new Pair<>(connection, phone);
-        sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_STARTED, argument));
+
+        /*
+         * Right now, assume that the device is connected to satellite via carrier within hysteresis
+         * time. However, this might not be correct when the monitoring timer expires. Thus, we
+         * should do this check now so that we have higher chance of sending the event
+         * EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer.
+         */
+        mIsSatelliteConnectedViaCarrierWithinHysteresisTime.set(
+                mSatelliteController.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_STARTED, connection));
     }
 
     /**
@@ -225,25 +210,34 @@
      */
     public void onEmergencyCallConnectionStateChanged(
             String callId, @Connection.ConnectionState int state) {
+        logd("callId=" + callId + ", state=" + state);
+        if (!mSatelliteController.isSatelliteSupportedViaOem()
+                && !mSatelliteController.isSatelliteSupportedViaCarrier()) {
+            logd("onEmergencyCallConnectionStateChanged: satellite is not supported");
+            return;
+        }
         Pair<String, Integer> argument = new Pair<>(callId, state);
         sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED, argument));
     }
 
-    private void handleEmergencyCallStartedEvent(@NonNull Pair<Connection, Phone> arg) {
-        mSatelliteController.requestIsSatelliteCommunicationAllowedForCurrentLocation(
-                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                mReceiverForRequestIsSatelliteAllowedForCurrentLocation);
-        if (mPhone != null) {
-            logd("handleEmergencyCallStartedEvent: new emergency call started while there is "
-                    + " an ongoing call");
-            unregisterForInterestedStateChangedEvents(mPhone);
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected ComponentName getDefaultSmsApp() {
+        return SmsApplication.getDefaultSmsApplication(mContext, false);
+    }
+
+    private void handleEmergencyCallStartedEvent(@NonNull Connection connection) {
+        if (sendEventDisplayEmergencyMessageForcefully(connection)) {
+            return;
         }
-        mPhone = arg.second;
-        mEmergencyConnection = arg.first;
-        mCellularServiceState.set(mPhone.getServiceState().getState());
-        mIsImsRegistered.set(mPhone.isImsRegistered());
-        handleStateChangedEventForHysteresisTimer();
-        registerForInterestedStateChangedEvents(mPhone);
+        if (mEmergencyConnection == null) {
+            handleStateChangedEventForHysteresisTimer();
+            registerForInterestedStateChangedEvents();
+        }
+        mEmergencyConnection = connection;
+        synchronized (mLock) {
+            mCheckingAccessRestrictionInProgress = false;
+            mIsSatelliteAllowedForCurrentLocation = false;
+        }
     }
 
     private void handleSatelliteProvisionStateChangedEvent(boolean provisioned) {
@@ -253,17 +247,74 @@
     }
 
     private void handleTimeoutEvent() {
-        boolean isDialerNotified = false;
-        if (!mIsImsRegistered.get() && !isCellularAvailable()
-                && mIsSatelliteAllowedInCurrentLocation.get()
-                && mSatelliteController.isSatelliteProvisioned()
-                && shouldTrackCall(mEmergencyConnection.getState())) {
-            logd("handleTimeoutEvent: Sending EVENT_DISPLAY_SOS_MESSAGE to Dialer...");
-            mEmergencyConnection.sendConnectionEvent(Call.EVENT_DISPLAY_SOS_MESSAGE, null);
-            isDialerNotified = true;
+        synchronized (mLock) {
+            mIsTimerTimedOut = true;
+            evaluateSendingConnectionEventDisplayEmergencyMessage();
         }
-        reportEsosRecommenderDecision(isDialerNotified);
-        cleanUpResources();
+    }
+
+    private void evaluateSendingConnectionEventDisplayEmergencyMessage() {
+        synchronized (mLock) {
+            if (mEmergencyConnection == null) {
+                loge("No emergency call is ongoing...");
+                return;
+            }
+
+            if (!mIsTimerTimedOut || mCheckingAccessRestrictionInProgress) {
+                logd("mIsTimerTimedOut=" + mIsTimerTimedOut
+                        + ", mCheckingAccessRestrictionInProgress="
+                        + mCheckingAccessRestrictionInProgress);
+                return;
+            }
+
+            /*
+             * The device might be connected to satellite after the emergency call started. Thus, we
+             * need to do this check again so that we will have higher chance of sending the event
+             * EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer.
+             */
+            updateSatelliteViaCarrierAvailability();
+
+            boolean isDialerNotified = false;
+            if (!isImsRegistered() && !isCellularAvailable()
+                    && isSatelliteAllowed()
+                    && (isSatelliteViaOemAvailable() || isSatelliteViaCarrierAvailable())
+                    && shouldTrackCall(mEmergencyConnection.getState())) {
+                logd("handleTimeoutEvent: Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer");
+                Bundle extras = createExtraBundleForEventDisplayEmergencyMessage();
+                mEmergencyConnection.sendConnectionEvent(
+                        TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE, extras);
+                isDialerNotified = true;
+
+            }
+            logd("handleTimeoutEvent: isImsRegistered=" + isImsRegistered()
+                    + ", isCellularAvailable=" + isCellularAvailable()
+                    + ", isSatelliteAllowed=" + isSatelliteAllowed()
+                    + ", shouldTrackCall=" + shouldTrackCall(mEmergencyConnection.getState()));
+            reportEsosRecommenderDecision(isDialerNotified);
+            cleanUpResources();
+        }
+    }
+
+    private boolean isSatelliteAllowed() {
+        synchronized (mLock) {
+            if (isSatelliteViaCarrierAvailable()) return true;
+            return mIsSatelliteAllowedForCurrentLocation;
+        }
+    }
+
+    private void updateSatelliteViaCarrierAvailability() {
+        if (!mIsSatelliteConnectedViaCarrierWithinHysteresisTime.get()) {
+            mIsSatelliteConnectedViaCarrierWithinHysteresisTime.set(
+                    mSatelliteController.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        }
+    }
+
+    private boolean isSatelliteViaOemAvailable() {
+        return mSatelliteController.isSatelliteViaOemProvisioned();
+    }
+
+    private boolean isSatelliteViaCarrierAvailable() {
+        return mIsSatelliteConnectedViaCarrierWithinHysteresisTime.get();
     }
 
     private void handleEmergencyCallConnectionStateChangedEvent(
@@ -279,7 +330,7 @@
             loge("handleEmergencyCallConnectionStateChangedEvent: unexpected state changed event "
                     + ", mEmergencyConnection=" + mEmergencyConnection + ", callId=" + callId
                     + ", state=" + state);
-            /**
+            /*
              * TelephonyConnectionService sent us a connection state changed event for a call that
              * we're not tracking. There must be some unexpected things happened in
              * TelephonyConnectionService. Thus, we need to clean up the resources.
@@ -291,21 +342,13 @@
         if (!shouldTrackCall(state)) {
             reportEsosRecommenderDecision(false);
             cleanUpResources();
-        }
-    }
-
-    private void handleImsRegistrationStateChangedEvent(boolean registered) {
-        if (registered != mIsImsRegistered.get()) {
-            mIsImsRegistered.set(registered);
-            handleStateChangedEventForHysteresisTimer();
-        }
-    }
-
-    private void handleCellularServiceStateChangedEvent(@NonNull ServiceState serviceState) {
-        int state = serviceState.getState();
-        if (mCellularServiceState.get() != state) {
-            mCellularServiceState.set(state);
-            handleStateChangedEventForHysteresisTimer();
+        } else {
+            // Location service will enter emergency mode only when connection state changes to
+            // STATE_DIALING
+            if (state == Connection.STATE_DIALING
+                    && mSatelliteController.isSatelliteSupportedViaOem()) {
+                requestIsSatelliteAllowedForCurrentLocation();
+            }
         }
     }
 
@@ -314,58 +357,116 @@
                 new SatelliteStats.SatelliteSosMessageRecommenderParams.Builder()
                         .setDisplaySosMessageSent(isDialerNotified)
                         .setCountOfTimerStarted(mCountOfTimerStarted)
-                        .setImsRegistered(mIsImsRegistered.get())
-                        .setCellularServiceState(mCellularServiceState.get())
+                        .setImsRegistered(isImsRegistered())
+                        .setCellularServiceState(getBestCellularServiceState())
+                        .setIsMultiSim(isMultiSim())
+                        .setRecommendingHandoverType(getEmergencyCallToSatelliteHandoverType())
+                        .setIsSatelliteAllowedInCurrentLocation(isSatelliteAllowed())
                         .build());
     }
 
     private void cleanUpResources() {
-        stopTimer();
-        if (mPhone != null) {
-            unregisterForInterestedStateChangedEvents(mPhone);
-            mPhone = null;
+        synchronized (mLock) {
+            stopTimer();
+            if (mEmergencyConnection != null) {
+                unregisterForInterestedStateChangedEvents();
+            }
+            mEmergencyConnection = null;
+            mCountOfTimerStarted = 0;
+            mIsTimerTimedOut = false;
+            mCheckingAccessRestrictionInProgress = false;
+            mIsSatelliteAllowedForCurrentLocation = false;
         }
-        mEmergencyConnection = null;
-        mCountOfTimerStarted = 0;
     }
 
-    private void registerForInterestedStateChangedEvents(@NonNull Phone phone) {
+    private void registerForInterestedStateChangedEvents() {
         mSatelliteController.registerForSatelliteProvisionStateChanged(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback);
-        phone.registerForServiceStateChanged(this, EVENT_CELLULAR_SERVICE_STATE_CHANGED, null);
-        registerForImsRegistrationStateChanged(phone);
+        for (Phone phone : PhoneFactory.getPhones()) {
+            phone.registerForServiceStateChanged(
+                    this, EVENT_SERVICE_STATE_CHANGED, null);
+            registerForImsRegistrationStateChanged(phone);
+        }
     }
 
     private void registerForImsRegistrationStateChanged(@NonNull Phone phone) {
         ImsManager imsManager = (mImsManager != null) ? mImsManager : ImsManager.getInstance(
                 phone.getContext(), phone.getPhoneId());
         try {
-            imsManager.addRegistrationCallback(mImsRegistrationCallback, this::post);
+            imsManager.addRegistrationCallback(
+                    getOrCreateImsRegistrationCallback(phone.getPhoneId()), this::post);
         } catch (ImsException ex) {
             loge("registerForImsRegistrationStateChanged: ex=" + ex);
         }
     }
 
-    private void unregisterForInterestedStateChangedEvents(@NonNull Phone phone) {
+    private void unregisterForInterestedStateChangedEvents() {
         mSatelliteController.unregisterForSatelliteProvisionStateChanged(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback);
-        phone.unregisterForServiceStateChanged(this);
-        unregisterForImsRegistrationStateChanged(phone);
+        for (Phone phone : PhoneFactory.getPhones()) {
+            phone.unregisterForServiceStateChanged(this);
+            unregisterForImsRegistrationStateChanged(phone);
+        }
     }
 
     private void unregisterForImsRegistrationStateChanged(@NonNull Phone phone) {
-        ImsManager imsManager = (mImsManager != null) ? mImsManager : ImsManager.getInstance(
-                phone.getContext(), phone.getPhoneId());
-        imsManager.removeRegistrationListener(mImsRegistrationCallback);
+        if (mImsRegistrationCallbacks.contains(phone.getPhoneId())) {
+            ImsManager imsManager =
+                    (mImsManager != null) ? mImsManager : ImsManager.getInstance(
+                            phone.getContext(), phone.getPhoneId());
+            imsManager.removeRegistrationListener(
+                    mImsRegistrationCallbacks.get(phone.getPhoneId()));
+        } else {
+            loge("Phone ID=" + phone.getPhoneId() + " was not registered with ImsManager");
+        }
     }
 
     private boolean isCellularAvailable() {
-        return (mCellularServiceState.get() == ServiceState.STATE_IN_SERVICE
-                || mCellularServiceState.get() == ServiceState.STATE_EMERGENCY_ONLY);
+        for (Phone phone : PhoneFactory.getPhones()) {
+            ServiceState serviceState = phone.getServiceState();
+            if (serviceState != null) {
+                int state = serviceState.getState();
+                if ((state == STATE_IN_SERVICE || state == STATE_EMERGENCY_ONLY)
+                        && !serviceState.isUsingNonTerrestrialNetwork()) {
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 
-    private void handleStateChangedEventForHysteresisTimer() {
-        if (!mIsImsRegistered.get() && !isCellularAvailable()) {
+    /**
+     * @return {@link ServiceState#STATE_IN_SERVICE} if any subscription is in this state; else
+     * {@link ServiceState#STATE_EMERGENCY_ONLY} if any subscription is in this state; else
+     * {@link ServiceState#STATE_OUT_OF_SERVICE}.
+     */
+    private int getBestCellularServiceState() {
+        boolean isStateOutOfService = true;
+        for (Phone phone : PhoneFactory.getPhones()) {
+            ServiceState serviceState = phone.getServiceState();
+            if (serviceState != null) {
+                int state = serviceState.getState();
+                if (!serviceState.isUsingNonTerrestrialNetwork()) {
+                    if ((state == STATE_IN_SERVICE)) {
+                        return STATE_IN_SERVICE;
+                    } else if (state == STATE_EMERGENCY_ONLY) {
+                        isStateOutOfService = false;
+                    }
+                }
+            }
+        }
+        return isStateOutOfService ? STATE_OUT_OF_SERVICE : STATE_EMERGENCY_ONLY;
+    }
+
+    private boolean isImsRegistered() {
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (phone.isImsRegistered()) return true;
+        }
+        return false;
+    }
+
+    private synchronized void handleStateChangedEventForHysteresisTimer() {
+        if (!isImsRegistered() && !isCellularAvailable()) {
             startTimer();
         } else {
             stopTimer();
@@ -373,21 +474,85 @@
     }
 
     private void startTimer() {
-        if (hasMessages(EVENT_TIME_OUT)) {
-            return;
+        synchronized (mLock) {
+            if (hasMessages(EVENT_TIME_OUT)) {
+                return;
+            }
+            sendMessageDelayed(obtainMessage(EVENT_TIME_OUT), mTimeoutMillis);
+            mCountOfTimerStarted++;
+            mIsTimerTimedOut = false;
         }
-        sendMessageDelayed(obtainMessage(EVENT_TIME_OUT), mTimeoutMillis);
-        mCountOfTimerStarted++;
     }
 
     private void stopTimer() {
-        removeMessages(EVENT_TIME_OUT);
+        synchronized (mLock) {
+            removeMessages(EVENT_TIME_OUT);
+        }
     }
 
-    private static long getEmergencyCallToSosMsgHysteresisTimeoutMillis() {
-        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
-                EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
-                DEFAULT_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+    private void handleSatelliteAccessRestrictionCheckingResult(boolean satelliteAllowed) {
+        synchronized (mLock) {
+            mIsSatelliteAllowedForCurrentLocation = satelliteAllowed;
+            mCheckingAccessRestrictionInProgress = false;
+            evaluateSendingConnectionEventDisplayEmergencyMessage();
+        }
+    }
+
+    private static long getEmergencyCallWaitForConnectionTimeoutMillis(@NonNull Context context) {
+        return context.getResources().getInteger(
+                R.integer.config_emergency_call_wait_for_connection_timeout_millis);
+    }
+
+    /**
+     * @return The Pair(PackageName, ClassName) of the oem-enabled satellite handover app.
+     */
+    @NonNull
+    private static Pair<String, String> getOemEnabledSatelliteHandoverAppFromOverlayConfig(
+            @NonNull Context context) {
+        String app = null;
+        try {
+            app = context.getResources().getString(
+                    R.string.config_oem_enabled_satellite_sos_handover_app);
+        } catch (Resources.NotFoundException ex) {
+            loge("getOemEnabledSatelliteHandoverAppFromOverlayConfig: ex=" + ex);
+        }
+        if (TextUtils.isEmpty(app) && isMockModemAllowed()) {
+            logd("getOemEnabledSatelliteHandoverAppFromOverlayConfig: Read "
+                    + "config_oem_enabled_satellite_sos_handover_app from device config");
+            app = DeviceConfig.getString(DeviceConfig.NAMESPACE_TELEPHONY,
+                    "config_oem_enabled_satellite_sos_handover_app", "");
+        }
+        if (TextUtils.isEmpty(app)) return new Pair<>("", "");
+
+        String[] appComponent = app.split(";");
+        if (appComponent.length == 2) {
+            return new Pair<>(appComponent[0], appComponent[1]);
+        } else {
+            loge("getOemEnabledSatelliteHandoverAppFromOverlayConfig: invalid configured app="
+                    + app);
+        }
+        return new Pair<>("", "");
+    }
+
+
+    @Nullable
+    private static String getSatelliteEmergencyHandoverIntentActionFromOverlayConfig(
+            @NonNull Context context) {
+        String action;
+        try {
+            action = context.getResources().getString(
+                    R.string.config_satellite_emergency_handover_intent_action);
+        } catch (Resources.NotFoundException ex) {
+            loge("getSatelliteEmergencyHandoverIntentFilterActionFromOverlayConfig: ex=" + ex);
+            action = null;
+        }
+        if (TextUtils.isEmpty(action) && isMockModemAllowed()) {
+            logd("getSatelliteEmergencyHandoverIntentActionFromOverlayConfig: Read "
+                    + "config_satellite_emergency_handover_intent_action from device config");
+            action = DeviceConfig.getString(DeviceConfig.NAMESPACE_TELEPHONY,
+                    "config_satellite_emergency_handover_intent_action", null);
+        }
+        return action;
     }
 
     private boolean shouldTrackCall(int connectionState) {
@@ -400,6 +565,146 @@
                 && connectionState != Connection.STATE_DISCONNECTED);
     }
 
+    @NonNull
+    private RegistrationManager.RegistrationCallback getOrCreateImsRegistrationCallback(
+            int phoneId) {
+        RegistrationManager.RegistrationCallback callback =
+                mImsRegistrationCallbacks.get(phoneId);
+        if (callback == null) {
+            callback = new RegistrationManager.RegistrationCallback() {
+                @Override
+                public void onRegistered(ImsRegistrationAttributes attributes) {
+                    sendMessage(obtainMessage(EVENT_SERVICE_STATE_CHANGED));
+                }
+
+                @Override
+                public void onUnregistered(ImsReasonInfo info) {
+                    sendMessage(obtainMessage(EVENT_SERVICE_STATE_CHANGED));
+                }
+            };
+            mImsRegistrationCallbacks.put(phoneId, callback);
+        }
+        return callback;
+    }
+
+    @NonNull private Bundle createExtraBundleForEventDisplayEmergencyMessage() {
+        int handoverType = EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
+        Pair<String, String> oemSatelliteMessagingApp =
+                getOemEnabledSatelliteHandoverAppFromOverlayConfig(mContext);
+        String packageName = oemSatelliteMessagingApp.first;
+        String className = oemSatelliteMessagingApp.second;
+        String action = getSatelliteEmergencyHandoverIntentActionFromOverlayConfig(mContext);
+
+        if (isSatelliteViaCarrierAvailable()
+                || isEmergencyCallToSatelliteHandoverTypeT911Enforced()) {
+            ComponentName defaultSmsAppComponent = getDefaultSmsApp();
+            handoverType = EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
+            packageName = defaultSmsAppComponent.getPackageName();
+            className = defaultSmsAppComponent.getClassName();
+        }
+        logd("EVENT_DISPLAY_EMERGENCY_MESSAGE: handoverType=" + handoverType + ", packageName="
+                + packageName + ", className=" + className + ", action=" + action);
+
+        Bundle result = new Bundle();
+        result.putInt(EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE, handoverType);
+        if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
+            result.putParcelable(EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT,
+                    createHandoverAppLaunchPendingIntent(packageName, className, action));
+        }
+        return result;
+    }
+
+    @NonNull private PendingIntent createHandoverAppLaunchPendingIntent(
+            @NonNull String packageName, @NonNull String className, @Nullable String action) {
+        Intent intent = new Intent(action);
+        intent.setComponent(new ComponentName(packageName, className));
+        return PendingIntent.getActivity(mContext, 0, intent,
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    private boolean isEmergencyCallToSatelliteHandoverTypeT911Enforced() {
+        return (mSatelliteController.getEnforcedEmergencyCallToSatelliteHandoverType()
+                == EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911);
+    }
+
+    private boolean sendEventDisplayEmergencyMessageForcefully(@NonNull Connection connection) {
+        if (mSatelliteController.getEnforcedEmergencyCallToSatelliteHandoverType()
+                == INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE) {
+            return false;
+        }
+
+        long delaySeconds = mSatelliteController.getDelayInSendingEventDisplayEmergencyMessage();
+        sendMessageDelayed(
+                obtainMessage(CMD_SEND_EVENT_DISPLAY_EMERGENCY_MESSAGE_FORCEFULLY, connection),
+                delaySeconds * 1000);
+        return true;
+    }
+
+    private void handleCmdSendEventDisplayEmergencyMessageForcefully(
+            @NonNull Connection connection) {
+        logd("Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer forcefully.");
+        Bundle extras = createExtraBundleForEventDisplayEmergencyMessage();
+        connection.sendConnectionEvent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE, extras);
+    }
+
+    private boolean isMultiSim() {
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        if (telephonyManager == null) {
+            loge("isMultiSim: telephonyManager is null");
+            return false;
+        }
+        return telephonyManager.isMultiSimEnabled();
+    }
+
+    private int getEmergencyCallToSatelliteHandoverType() {
+        if (isSatelliteViaCarrierAvailable()) {
+            return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
+        } else {
+            return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
+        }
+    }
+
+    private void requestIsSatelliteAllowedForCurrentLocation() {
+        synchronized (mLock) {
+            if (mCheckingAccessRestrictionInProgress) {
+                logd("requestIsSatelliteCommunicationAllowedForCurrentLocation was already sent");
+                return;
+            }
+            mCheckingAccessRestrictionInProgress = true;
+        }
+
+        OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback =
+                new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(Boolean result) {
+                        logd("requestIsSatelliteAllowedForCurrentLocation: result=" + result);
+                        sendMessage(obtainMessage(
+                                EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT, result));
+                    }
+
+                    @Override
+                    public void onError(SatelliteManager.SatelliteException ex) {
+                        logd("requestIsSatelliteAllowedForCurrentLocation: onError, ex=" + ex);
+                        sendMessage(obtainMessage(
+                                EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT, false));
+                    }
+                };
+        requestIsSatelliteCommunicationAllowedForCurrentLocation(callback);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void requestIsSatelliteCommunicationAllowedForCurrentLocation(
+            @NonNull OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback) {
+        SatelliteManager satelliteManager = mContext.getSystemService(SatelliteManager.class);
+        satelliteManager.requestIsCommunicationAllowedForCurrentLocation(
+                this::post, callback);
+    }
+
+    private static boolean isMockModemAllowed() {
+        return (SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)
+                || SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false));
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
index 151b69d..0e6f706 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
@@ -19,6 +19,8 @@
 import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
 import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
 
+import static java.util.stream.Collectors.joining;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -29,23 +31,23 @@
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.satellite.AntennaPosition;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.PointingInfo;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.stub.NTRadioTechnology;
-import android.telephony.satellite.stub.SatelliteError;
 import android.telephony.satellite.stub.SatelliteModemState;
+import android.telephony.satellite.stub.SatelliteResult;
 
-import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.RILUtils;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -81,53 +83,51 @@
     /**
      * Convert satellite error from service definition to framework definition.
      * @param error The SatelliteError from the satellite service.
-     * @return The converted SatelliteError for the framework.
+     * @return The converted SatelliteResult for the framework.
      */
-    @SatelliteManager.SatelliteError public static int fromSatelliteError(int error) {
+    @SatelliteManager.SatelliteResult public static int fromSatelliteError(int error) {
         switch (error) {
-            case SatelliteError.ERROR_NONE:
-                return SatelliteManager.SATELLITE_ERROR_NONE;
-            case SatelliteError.SATELLITE_ERROR:
-                return SatelliteManager.SATELLITE_ERROR;
-            case SatelliteError.SERVER_ERROR:
-                return SatelliteManager.SATELLITE_SERVER_ERROR;
-            case SatelliteError.SERVICE_ERROR:
-                return SatelliteManager.SATELLITE_SERVICE_ERROR;
-            case SatelliteError.MODEM_ERROR:
-                return SatelliteManager.SATELLITE_MODEM_ERROR;
-            case SatelliteError.NETWORK_ERROR:
-                return SatelliteManager.SATELLITE_NETWORK_ERROR;
-            case SatelliteError.INVALID_TELEPHONY_STATE:
-                return SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
-            case SatelliteError.INVALID_MODEM_STATE:
-                return SatelliteManager.SATELLITE_INVALID_MODEM_STATE;
-            case SatelliteError.INVALID_ARGUMENTS:
-                return SatelliteManager.SATELLITE_INVALID_ARGUMENTS;
-            case SatelliteError.REQUEST_FAILED:
-                return SatelliteManager.SATELLITE_REQUEST_FAILED;
-            case SatelliteError.RADIO_NOT_AVAILABLE:
-                return SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE;
-            case SatelliteError.REQUEST_NOT_SUPPORTED:
-                return SatelliteManager.SATELLITE_REQUEST_NOT_SUPPORTED;
-            case SatelliteError.NO_RESOURCES:
-                return SatelliteManager.SATELLITE_NO_RESOURCES;
-            case SatelliteError.SERVICE_NOT_PROVISIONED:
-                return SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED;
-            case SatelliteError.SERVICE_PROVISION_IN_PROGRESS:
-                return SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS;
-            case SatelliteError.REQUEST_ABORTED:
-                return SatelliteManager.SATELLITE_REQUEST_ABORTED;
-            case SatelliteError.SATELLITE_ACCESS_BARRED:
-                return SatelliteManager.SATELLITE_ACCESS_BARRED;
-            case SatelliteError.NETWORK_TIMEOUT:
-                return SatelliteManager.SATELLITE_NETWORK_TIMEOUT;
-            case SatelliteError.SATELLITE_NOT_REACHABLE:
-                return SatelliteManager.SATELLITE_NOT_REACHABLE;
-            case SatelliteError.NOT_AUTHORIZED:
-                return SatelliteManager.SATELLITE_NOT_AUTHORIZED;
+            case SatelliteResult.SATELLITE_RESULT_SUCCESS:
+                return SatelliteManager.SATELLITE_RESULT_SUCCESS;
+            case SatelliteResult.SATELLITE_RESULT_ERROR:
+                return SatelliteManager.SATELLITE_RESULT_ERROR;
+            case SatelliteResult.SATELLITE_RESULT_SERVER_ERROR:
+                return SatelliteManager.SATELLITE_RESULT_SERVER_ERROR;
+            case SatelliteResult.SATELLITE_RESULT_SERVICE_ERROR:
+                return SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR;
+            case SatelliteResult.SATELLITE_RESULT_MODEM_ERROR:
+                return SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
+            case SatelliteResult.SATELLITE_RESULT_NETWORK_ERROR:
+                return SatelliteManager.SATELLITE_RESULT_NETWORK_ERROR;
+            case SatelliteResult.SATELLITE_RESULT_INVALID_MODEM_STATE:
+                return SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE;
+            case SatelliteResult.SATELLITE_RESULT_INVALID_ARGUMENTS:
+                return SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS;
+            case SatelliteResult.SATELLITE_RESULT_REQUEST_FAILED:
+                return SatelliteManager.SATELLITE_RESULT_REQUEST_FAILED;
+            case SatelliteResult.SATELLITE_RESULT_RADIO_NOT_AVAILABLE:
+                return SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE;
+            case SatelliteResult.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED:
+                return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+            case SatelliteResult.SATELLITE_RESULT_NO_RESOURCES:
+                return SatelliteManager.SATELLITE_RESULT_NO_RESOURCES;
+            case SatelliteResult.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED:
+                return SatelliteManager.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED;
+            case SatelliteResult.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS:
+                return SatelliteManager.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS;
+            case SatelliteResult.SATELLITE_RESULT_REQUEST_ABORTED:
+                return SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED;
+            case SatelliteResult.SATELLITE_RESULT_ACCESS_BARRED:
+                return SatelliteManager.SATELLITE_RESULT_ACCESS_BARRED;
+            case SatelliteResult.SATELLITE_RESULT_NETWORK_TIMEOUT:
+                return SatelliteManager.SATELLITE_RESULT_NETWORK_TIMEOUT;
+            case SatelliteResult.SATELLITE_RESULT_NOT_REACHABLE:
+                return SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE;
+            case SatelliteResult.SATELLITE_RESULT_NOT_AUTHORIZED:
+                return SatelliteManager.SATELLITE_RESULT_NOT_AUTHORIZED;
         }
         loge("Received invalid satellite service error: " + error);
-        return SatelliteManager.SATELLITE_SERVICE_ERROR;
+        return SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR;
     }
 
     /**
@@ -150,6 +150,10 @@
                 return SatelliteManager.SATELLITE_MODEM_STATE_OFF;
             case SatelliteModemState.SATELLITE_MODEM_STATE_UNAVAILABLE:
                 return SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
+            case SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED:
+                return SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
+            case SatelliteModemState.SATELLITE_MODEM_STATE_CONNECTED:
+                return SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
             default:
                 loge("Received invalid modem state: " + modemState);
                 return SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
@@ -209,6 +213,16 @@
     }
 
     /**
+     * Convert non-terrestrial signal strength from service definition to framework definition.
+     * @param ntnSignalStrength The non-terrestrial signal strength from the satellite service.
+     * @return The converted non-terrestrial signal strength for the framework.
+     */
+    @Nullable public static NtnSignalStrength fromNtnSignalStrength(
+            android.telephony.satellite.stub.NtnSignalStrength ntnSignalStrength) {
+        return new NtnSignalStrength(ntnSignalStrength.signalStrengthLevel);
+    }
+
+    /**
      * Convert SatelliteDatagram from framework definition to service definition.
      * @param datagram The SatelliteDatagram from the framework.
      * @return The converted SatelliteDatagram for the satellite service.
@@ -222,25 +236,21 @@
     }
 
     /**
-     * Get the {@link SatelliteManager.SatelliteError} from the provided result.
+     * Get the {@link SatelliteManager.SatelliteResult} from the provided result.
      *
      * @param ar AsyncResult used to determine the error code.
      * @param caller The satellite request.
      *
-     * @return The {@link SatelliteManager.SatelliteError} error code from the request.
+     * @return The {@link SatelliteManager.SatelliteResult} error code from the request.
      */
-    @SatelliteManager.SatelliteError public static int getSatelliteError(@NonNull AsyncResult ar,
+    @SatelliteManager.SatelliteResult public static int getSatelliteError(@NonNull AsyncResult ar,
             @NonNull String caller) {
         int errorCode;
         if (ar.exception == null) {
-            errorCode = SatelliteManager.SATELLITE_ERROR_NONE;
+            errorCode = SatelliteManager.SATELLITE_RESULT_SUCCESS;
         } else {
-            errorCode = SatelliteManager.SATELLITE_ERROR;
-            if (ar.exception instanceof CommandException) {
-                CommandException.Error error = ((CommandException) ar.exception).getCommandError();
-                errorCode = RILUtils.convertToSatelliteError(error);
-                loge(caller + " CommandException: " + ar.exception);
-            } else if (ar.exception instanceof SatelliteManager.SatelliteException) {
+            errorCode = SatelliteManager.SATELLITE_RESULT_ERROR;
+            if (ar.exception instanceof SatelliteManager.SatelliteException) {
                 errorCode = ((SatelliteManager.SatelliteException) ar.exception).getErrorCode();
                 loge(caller + " SatelliteException: " + ar.exception);
             } else {
@@ -274,58 +284,6 @@
     }
 
     /**
-     * Expected format of each input string in the array: "PLMN_1:service_1,service_2,..."
-     *
-     * @return The map of supported services with key: PLMN, value: set of services supported by
-     * the PLMN.
-     */
-    @NonNull
-    @NetworkRegistrationInfo.ServiceType
-    public static Map<String, Set<Integer>> parseSupportedSatelliteServices(
-            String[] supportedSatelliteServicesStrArray) {
-        Map<String, Set<Integer>> supportedServicesMap = new HashMap<>();
-        if (supportedSatelliteServicesStrArray == null
-                || supportedSatelliteServicesStrArray.length == 0) {
-            return supportedServicesMap;
-        }
-
-        for (String supportedServicesPerPlmnStr : supportedSatelliteServicesStrArray) {
-            String[] pairOfPlmnAndsupportedServicesStr =
-                    supportedServicesPerPlmnStr.split(":");
-            if (pairOfPlmnAndsupportedServicesStr != null
-                    && (pairOfPlmnAndsupportedServicesStr.length == 1
-                    || pairOfPlmnAndsupportedServicesStr.length == 2)) {
-                String plmn = pairOfPlmnAndsupportedServicesStr[0];
-                Set<Integer> supportedServicesSet = new HashSet<>();
-                if (pairOfPlmnAndsupportedServicesStr.length == 2) {
-                    String[] supportedServicesStrArray =
-                            pairOfPlmnAndsupportedServicesStr[1].split(",");
-                    for (String service : supportedServicesStrArray) {
-                        try {
-                            int serviceType = Integer.parseInt(service);
-                            if (isServiceTypeValid(serviceType)) {
-                                supportedServicesSet.add(serviceType);
-                            } else {
-                                loge("parseSupportedSatelliteServices: invalid serviceType="
-                                        + serviceType);
-                            }
-                        } catch (NumberFormatException e) {
-                            loge("parseSupportedSatelliteServices: supportedServicesPerPlmnStr="
-                                    + supportedServicesPerPlmnStr + ", service=" + service
-                                    + ", e=" + e);
-                        }
-                    }
-                }
-                supportedServicesMap.put(plmn, supportedServicesSet);
-            } else {
-                loge("parseSupportedSatelliteServices: invalid format input, "
-                        + "supportedServicesPerPlmnStr=" + supportedServicesPerPlmnStr);
-            }
-        }
-        return supportedServicesMap;
-    }
-
-    /**
      * Expected format of the input dictionary bundle is:
      * <ul>
      *     <li>Key: PLMN string.</li>
@@ -353,41 +311,23 @@
                             + " for plmn=" + plmn);
                 }
             }
+            logd("parseSupportedSatelliteServices: plmn=" + plmn + ", supportedServicesSet="
+                    + supportedServicesSet.stream().map(String::valueOf).collect(
+                            joining(",")));
             supportedServicesMap.put(plmn, supportedServicesSet);
         }
         return supportedServicesMap;
     }
 
     /**
-     * For the PLMN that exists in both {@code providerSupportedServices} and
-     * {@code carrierSupportedServices}, the supported services will be the intersection of the two
-     * sets. For the PLMN that is present in {@code providerSupportedServices} but not in
-     * {@code carrierSupportedServices}, the provider supported services will be used. The rest
-     * will not be used.
-     *
-     * @param providerSupportedServices Satellite provider supported satellite services.
-     * @param carrierSupportedServices Carrier supported satellite services.
-     * @return The supported satellite services by the device for the corresponding carrier and the
-     * satellite provider.
+     * Merge two string lists into one such that the result list does not have any duplicate items.
      */
     @NonNull
-    @NetworkRegistrationInfo.ServiceType
-    public static Map<String, Set<Integer>> mergeSupportedSatelliteServices(
-            @NonNull @NetworkRegistrationInfo.ServiceType Map<String, Set<Integer>>
-                    providerSupportedServices,
-            @NonNull @NetworkRegistrationInfo.ServiceType Map<String, Set<Integer>>
-                    carrierSupportedServices) {
-        Map<String, Set<Integer>> supportedServicesMap = new HashMap<>();
-        for (Map.Entry<String, Set<Integer>> entry : providerSupportedServices.entrySet()) {
-            Set<Integer> supportedServices = new HashSet<>(entry.getValue());
-            if (carrierSupportedServices.containsKey(entry.getKey())) {
-                supportedServices.retainAll(carrierSupportedServices.get(entry.getKey()));
-            }
-            if (!supportedServices.isEmpty()) {
-                supportedServicesMap.put(entry.getKey(), supportedServices);
-            }
-        }
-        return supportedServicesMap;
+    public static List<String> mergeStrLists(List<String> strList1, List<String> strList2) {
+        Set<String> mergedStrSet = new HashSet<>();
+        mergedStrSet.addAll(strList1);
+        mergedStrSet.addAll(strList2);
+        return mergedStrSet.stream().toList();
     }
 
     private static boolean isServiceTypeValid(int serviceType) {
@@ -403,6 +343,15 @@
         return PhoneFactory.getPhone(0);
     }
 
+    /**
+     * Return phone associated with subscription ID.
+     *
+     * @return phone associated with {@code subId} or {@code null} if it doesn't exist.
+     */
+    public static @Nullable Phone getPhone(int subId) {
+        return PhoneFactory.getPhone(subId);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
index 36ad250..5c79c28 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS;
@@ -23,6 +24,7 @@
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,15 +32,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.AsyncResult;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.provider.DeviceConfig;
 import android.telephony.Rlog;
-import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.stub.ISatelliteGateway;
 import android.telephony.satellite.stub.SatelliteGatewayService;
@@ -46,6 +48,7 @@
 import android.util.Log;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.ExponentialBackoff;
 import com.android.internal.util.State;
@@ -95,6 +98,9 @@
     private static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 1;
     private static final int EVENT_LISTENING_TIMER_TIMEOUT = 2;
     private static final int EVENT_SATELLITE_ENABLED_STATE_CHANGED = 3;
+    private static final int EVENT_SATELLITE_MODEM_STATE_CHANGED = 4;
+    private static final int EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE = 5;
+    protected static final int EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT = 6;
 
     private static final long REBIND_INITIAL_DELAY = 2 * 1000; // 2 seconds
     private static final long REBIND_MAXIMUM_DELAY = 64 * 1000; // 1 minute
@@ -117,14 +123,21 @@
     @NonNull private final IdleState mIdleState = new IdleState();
     @NonNull private final TransferringState mTransferringState = new TransferringState();
     @NonNull private final ListeningState mListeningState = new ListeningState();
+    @NonNull private final NotConnectedState mNotConnectedState = new NotConnectedState();
+    @NonNull private final ConnectedState mConnectedState = new ConnectedState();
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected AtomicBoolean mIsSendingTriggeredDuringTransferringState;
     private long mSatelliteStayAtListeningFromSendingMillis;
     private long mSatelliteStayAtListeningFromReceivingMillis;
-    private final ConcurrentHashMap<IBinder, ISatelliteStateCallback> mListeners;
+    private long mSatelliteNbIotInactivityTimeoutMillis;
+    private final ConcurrentHashMap<IBinder, ISatelliteModemStateCallback> mListeners;
     @SatelliteManager.SatelliteModemState private int mCurrentState;
     final boolean mIsSatelliteSupported;
     private boolean mIsDemoMode = false;
+    @GuardedBy("mLock")
+    @NonNull private boolean mIsDisableCellularModemInProgress = false;
+    @NonNull private final SatelliteController mSatelliteController;
+    @NonNull private final DatagramController mDatagramController;
 
     /**
      * @return The singleton instance of SatelliteSessionController.
@@ -148,9 +161,7 @@
             @NonNull Context context, @NonNull Looper looper, boolean isSatelliteSupported) {
         if (sInstance == null) {
             sInstance = new SatelliteSessionController(context, looper, isSatelliteSupported,
-                    SatelliteModemInterface.getInstance(),
-                    getSatelliteStayAtListeningFromSendingMillis(),
-                    getSatelliteStayAtListeningFromReceivingMillis());
+                    SatelliteModemInterface.getInstance());
         } else {
             if (isSatelliteSupported != sInstance.mIsSatelliteSupported) {
                 Rlog.e(TAG, "New satellite support state " + isSatelliteSupported
@@ -168,23 +179,22 @@
      * @param looper The looper associated with the handler of this class.
      * @param isSatelliteSupported Whether satellite is supported on the device.
      * @param satelliteModemInterface The singleton of SatelliteModemInterface.
-     * @param satelliteStayAtListeningFromSendingMillis The duration to stay at listening mode when
-     *                                                    transitioning from sending mode.
-     * @param satelliteStayAtListeningFromReceivingMillis The duration to stay at listening mode
-     *                                                    when transitioning from receiving mode.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected SatelliteSessionController(@NonNull Context context, @NonNull Looper looper,
             boolean isSatelliteSupported,
-            @NonNull SatelliteModemInterface satelliteModemInterface,
-            long satelliteStayAtListeningFromSendingMillis,
-            long satelliteStayAtListeningFromReceivingMillis) {
+            @NonNull SatelliteModemInterface satelliteModemInterface) {
         super(TAG, looper);
 
         mContext = context;
         mSatelliteModemInterface = satelliteModemInterface;
-        mSatelliteStayAtListeningFromSendingMillis = satelliteStayAtListeningFromSendingMillis;
-        mSatelliteStayAtListeningFromReceivingMillis = satelliteStayAtListeningFromReceivingMillis;
+        mSatelliteController = SatelliteController.getInstance();
+        mDatagramController = DatagramController.getInstance();
+        mSatelliteStayAtListeningFromSendingMillis = getSatelliteStayAtListeningFromSendingMillis();
+        mSatelliteStayAtListeningFromReceivingMillis =
+                getSatelliteStayAtListeningFromReceivingMillis();
+        mSatelliteNbIotInactivityTimeoutMillis =
+                getSatelliteNbIotInactivityTimeoutMillis();
         mListeners = new ConcurrentHashMap<>();
         mIsSendingTriggeredDuringTransferringState = new AtomicBoolean(false);
         mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
@@ -211,6 +221,8 @@
         addState(mIdleState);
         addState(mTransferringState);
         addState(mListeningState, mTransferringState);
+        addState(mNotConnectedState);
+        addState(mConnectedState);
         setInitialState(isSatelliteSupported);
         start();
     }
@@ -245,11 +257,23 @@
     }
 
     /**
+     * {@link SatelliteController} uses this function to notify {@link SatelliteSessionController}
+     * that the satellite modem state has changed.
+     *
+     * @param state The current state of the satellite modem.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
+        sendMessage(EVENT_SATELLITE_MODEM_STATE_CHANGED, state);
+    }
+
+    /**
      * Registers for modem state changed from satellite modem.
      *
      * @param callback The callback to handle the satellite modem state changed event.
      */
-    public void registerForSatelliteModemStateChanged(@NonNull ISatelliteStateCallback callback) {
+    public void registerForSatelliteModemStateChanged(
+            @NonNull ISatelliteModemStateCallback callback) {
         try {
             callback.onSatelliteModemStateChanged(mCurrentState);
             mListeners.put(callback.asBinder(), callback);
@@ -263,9 +287,10 @@
      * If callback was not registered before, the request will be ignored.
      *
      * @param callback The callback that was passed to
-     *                 {@link #registerForSatelliteModemStateChanged(ISatelliteStateCallback)}.
+     * {@link #registerForSatelliteModemStateChanged(ISatelliteModemStateCallback)}.
      */
-    public void unregisterForSatelliteModemStateChanged(@NonNull ISatelliteStateCallback callback) {
+    public void unregisterForSatelliteModemStateChanged(
+            @NonNull ISatelliteModemStateCallback callback) {
         mListeners.remove(callback.asBinder());
     }
 
@@ -289,9 +314,12 @@
                     getSatelliteStayAtListeningFromSendingMillis();
             mSatelliteStayAtListeningFromReceivingMillis =
                     getSatelliteStayAtListeningFromReceivingMillis();
+            mSatelliteNbIotInactivityTimeoutMillis =
+                    getSatelliteNbIotInactivityTimeoutMillis();
         } else {
             mSatelliteStayAtListeningFromSendingMillis = timeoutMillis;
             mSatelliteStayAtListeningFromReceivingMillis = timeoutMillis;
+            mSatelliteNbIotInactivityTimeoutMillis = timeoutMillis;
         }
 
         return true;
@@ -330,6 +358,7 @@
         }
         return true;
     }
+
     /**
      * Adjusts listening timeout duration when demo mode is on
      *
@@ -363,6 +392,7 @@
         public void enter() {
             if (DBG) logd("Entering UnavailableState");
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
+            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE);
         }
 
         @Override
@@ -379,7 +409,11 @@
 
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_OFF;
             mIsSendingTriggeredDuringTransferringState.set(false);
+            synchronized (mLock) {
+                mIsDisableCellularModemInProgress = false;
+            }
             unbindService();
+            stopNbIotInactivityTimer();
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         }
 
@@ -404,7 +438,11 @@
 
         private void handleSatelliteEnabledStateChanged(boolean on) {
             if (on) {
-                transitionTo(mIdleState);
+                if (mSatelliteController.isSatelliteAttachRequired()) {
+                    transitionTo(mNotConnectedState);
+                } else {
+                    transitionTo(mIdleState);
+                }
             }
         }
     }
@@ -415,6 +453,7 @@
             if (DBG) logd("Entering IdleState");
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
             mIsSendingTriggeredDuringTransferringState.set(false);
+            stopNbIotInactivityTimer();
             //Enable Cellular Modem scanning
             mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(true, null);
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
@@ -430,6 +469,10 @@
                 case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
                     handleSatelliteEnabledStateChanged(!(boolean) msg.obj, "IdleState");
                     break;
+                case EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE:
+                    handleEventDisableCellularModemWhileSatelliteModeIsOnDone(
+                            (AsyncResult) msg.obj);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -440,15 +483,61 @@
             if ((datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING)
                     || (datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING)) {
-                transitionTo(mTransferringState);
+                if (mSatelliteController.isSatelliteAttachRequired()) {
+                    loge("Unexpected transferring state received for NB-IOT NTN");
+                } else {
+                    transitionTo(mTransferringState);
+                }
+            } else if ((datagramTransferState.sendState
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT)
+                    || (datagramTransferState.receiveState
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT)) {
+                if (mSatelliteController.isSatelliteAttachRequired()) {
+                    disableCellularModemWhileSatelliteModeIsOn();
+                } else {
+                    loge("Unexpected transferring state received for non-NB-IOT NTN");
+                }
+            }
+        }
+
+        private void handleEventDisableCellularModemWhileSatelliteModeIsOnDone(
+                @NonNull AsyncResult result) {
+            synchronized (mLock) {
+                if (mIsDisableCellularModemInProgress) {
+                    int error = SatelliteServiceUtils.getSatelliteError(
+                            result, "DisableCellularModemWhileSatelliteModeIsOnDone");
+                    if (error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+                        transitionTo(mNotConnectedState);
+                    }
+                    mIsDisableCellularModemInProgress = false;
+                } else {
+                    loge("DisableCellularModemWhileSatelliteModeIsOn is not in progress");
+                }
+            }
+        }
+
+        private void disableCellularModemWhileSatelliteModeIsOn() {
+            synchronized (mLock) {
+                if (mIsDisableCellularModemInProgress) {
+                    logd("Cellular scanning is already being disabled");
+                    return;
+                }
+
+                mIsDisableCellularModemInProgress = true;
+                Message onCompleted =
+                        obtainMessage(EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE);
+                mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false,
+                        onCompleted);
             }
         }
 
         @Override
         public void exit() {
             if (DBG) logd("Exiting IdleState");
-            //Disable Cellular Modem Scanning
-            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false, null);
+            if (!mSatelliteController.isSatelliteAttachRequired()) {
+                // Disable cellular modem scanning
+                mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false, null);
+            }
         }
     }
 
@@ -456,6 +545,7 @@
         @Override
         public void enter() {
             if (DBG) logd("Entering TransferringState");
+            stopNbIotInactivityTimer();
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
         }
@@ -470,6 +560,9 @@
                 case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
                     handleSatelliteEnabledStateChanged(!(boolean) msg.obj, "TransferringState");
                     break;
+                case EVENT_SATELLITE_MODEM_STATE_CHANGED:
+                    handleEventSatelliteModemStateChange(msg.arg1);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -480,13 +573,26 @@
             if (isSending(datagramTransferState.sendState) || isReceiving(
                     datagramTransferState.receiveState)) {
                 // Stay at transferring state.
-            } else if ((datagramTransferState.sendState
-                    == SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED)
-                    || (datagramTransferState.receiveState
-                    == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED)) {
-                transitionTo(mIdleState);
             } else {
-                transitionTo(mListeningState);
+                if (mSatelliteController.isSatelliteAttachRequired()) {
+                    transitionTo(mConnectedState);
+                } else {
+                    if ((datagramTransferState.sendState
+                            == SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED)
+                            || (datagramTransferState.receiveState
+                            == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED)) {
+                        transitionTo(mIdleState);
+                    } else {
+                        transitionTo(mListeningState);
+                    }
+                }
+            }
+        }
+
+        private void handleEventSatelliteModemStateChange(
+                @SatelliteManager.SatelliteModemState int state) {
+            if (state == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED) {
+                transitionTo(mNotConnectedState);
             }
         }
     }
@@ -505,6 +611,8 @@
 
         @Override
         public void exit() {
+            if (DBG) logd("Exiting ListeningState");
+
             removeMessages(EVENT_LISTENING_TIMER_TIMEOUT);
             updateListeningMode(false);
         }
@@ -549,6 +657,121 @@
         }
     }
 
+    private class NotConnectedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) logd("Entering NotConnectedState");
+
+            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
+            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+            startNbIotInactivityTimer();
+        }
+
+        @Override
+        public void exit() {
+            if (DBG) logd("Exiting NotConnectedState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("NotConnectedState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
+                    handleSatelliteEnabledStateChanged(
+                            !(boolean) msg.obj, "NotConnectedState");
+                    break;
+                case EVENT_SATELLITE_MODEM_STATE_CHANGED:
+                    handleEventSatelliteModemStateChanged(msg.arg1);
+                    break;
+                case EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT:
+                    transitionTo(mIdleState);
+                    break;
+                case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
+                    handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleEventSatelliteModemStateChanged(
+                @SatelliteManager.SatelliteModemState int state) {
+            if (state == SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED) {
+                transitionTo(mConnectedState);
+            }
+        }
+
+        private void handleEventDatagramTransferStateChanged(
+                @NonNull DatagramTransferState datagramTransferState) {
+            if (datagramTransferState.sendState
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT
+                    || datagramTransferState.receiveState
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT) {
+                stopNbIotInactivityTimer();
+            } else if (datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
+                    && datagramTransferState.receiveState
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE) {
+                startNbIotInactivityTimer();
+            } else if (isSending(datagramTransferState.sendState)
+                    || isReceiving(datagramTransferState.receiveState)) {
+                restartNbIotInactivityTimer();
+            }
+        }
+    }
+
+    private class ConnectedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) logd("Entering ConnectedState");
+
+            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+            startNbIotInactivityTimer();
+        }
+
+        @Override
+        public void exit() {
+            if (DBG) logd("Exiting ConnectedState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("ConnectedState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
+                    handleSatelliteEnabledStateChanged(
+                            !(boolean) msg.obj, "ConnectedState");
+                    break;
+                case EVENT_SATELLITE_MODEM_STATE_CHANGED:
+                    handleEventSatelliteModemStateChanged(msg.arg1);
+                    break;
+                case EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT:
+                    transitionTo(mIdleState);
+                    break;
+                case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
+                    handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleEventSatelliteModemStateChanged(
+                @SatelliteManager.SatelliteModemState int state) {
+            if (state == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED) {
+                transitionTo(mNotConnectedState);
+            }
+        }
+
+        private void handleEventDatagramTransferStateChanged(
+                @NonNull DatagramTransferState datagramTransferState) {
+            if (isSending(datagramTransferState.sendState)
+                    || isReceiving(datagramTransferState.receiveState)) {
+                transitionTo(mTransferringState);
+            }
+        }
+    }
+
     /**
      * @return the string for msg.what
      */
@@ -565,6 +788,15 @@
             case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
                 whatString = "EVENT_SATELLITE_ENABLED_STATE_CHANGED";
                 break;
+            case EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE:
+                whatString = "EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE";
+                break;
+            case EVENT_SATELLITE_MODEM_STATE_CHANGED:
+                whatString = "EVENT_SATELLITE_MODEM_STATE_CHANGED";
+                break;
+            case EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT:
+                whatString = "EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT";
+                break;
             default:
                 whatString = "UNKNOWN EVENT " + what;
         }
@@ -580,7 +812,9 @@
     }
 
     private void notifyStateChangedEvent(@SatelliteManager.SatelliteModemState int state) {
-        List<ISatelliteStateCallback> toBeRemoved = new ArrayList<>();
+        mDatagramController.onSatelliteModemStateChanged(state);
+
+        List<ISatelliteModemStateCallback> toBeRemoved = new ArrayList<>();
         mListeners.values().forEach(listener -> {
             try {
                 listener.onSatelliteModemStateChanged(state);
@@ -720,23 +954,54 @@
         return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
     }
 
-    private static long getSatelliteStayAtListeningFromSendingMillis() {
-        if (sInstance != null && sInstance.isDemoMode()) {
+    private long getSatelliteStayAtListeningFromSendingMillis() {
+        if (isDemoMode()) {
             return DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS;
         } else {
-            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
-                    SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS,
-                    DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS);
+            return mContext.getResources().getInteger(
+                    R.integer.config_satellite_stay_at_listening_from_sending_millis);
         }
     }
 
-    private static long getSatelliteStayAtListeningFromReceivingMillis() {
-        if (sInstance != null && sInstance.isDemoMode()) {
+    private long getSatelliteStayAtListeningFromReceivingMillis() {
+        if (isDemoMode()) {
             return DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS;
         } else {
-            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
-                    SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS,
-                    DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS);
+            return mContext.getResources().getInteger(
+                    R.integer.config_satellite_stay_at_listening_from_receiving_millis);
         }
     }
+
+    private long getSatelliteNbIotInactivityTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_satellite_nb_iot_inactivity_timeout_millis);
+    }
+
+    private void restartNbIotInactivityTimer() {
+        stopNbIotInactivityTimer();
+        startNbIotInactivityTimer();
+    }
+
+    private void startNbIotInactivityTimer() {
+        if (isNbIotInactivityTimerStarted()) {
+            logd("NB IOT inactivity timer is already started");
+            return;
+        }
+
+        DatagramController datagramController = DatagramController.getInstance();
+        if (datagramController.isSendingInIdleState()
+                && datagramController.isPollingInIdleState()) {
+            sendMessageDelayed(
+                    EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT,
+                    mSatelliteNbIotInactivityTimeoutMillis);
+        }
+    }
+
+    private void stopNbIotInactivityTimer() {
+        removeMessages(EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT);
+    }
+
+    private boolean isNbIotInactivityTimerStarted() {
+        return hasMessages(EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
index 7a1de7c..0c18fac 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
@@ -178,9 +178,9 @@
 
     /** Report a counter when an attempt for incoming datagram is failed */
     public void reportIncomingDatagramCount(
-            @NonNull @SatelliteManager.SatelliteError int result) {
+            @NonNull @SatelliteManager.SatelliteResult int result) {
         SatelliteStats.SatelliteControllerParams controllerParam;
-        if (result == SatelliteManager.SATELLITE_ERROR_NONE) {
+        if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
                     .setCountOfIncomingDatagramSuccess(ADD_COUNT)
                     .build();
@@ -194,9 +194,9 @@
     }
 
     /** Report a counter when an attempt for de-provision is success or not */
-    public void reportProvisionCount(@NonNull @SatelliteManager.SatelliteError int result) {
+    public void reportProvisionCount(@NonNull @SatelliteManager.SatelliteResult int result) {
         SatelliteStats.SatelliteControllerParams controllerParam;
-        if (result == SatelliteManager.SATELLITE_ERROR_NONE) {
+        if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
                     .setCountOfProvisionSuccess(ADD_COUNT)
                     .build();
@@ -210,9 +210,9 @@
     }
 
     /** Report a counter when an attempt for de-provision is success or not */
-    public void reportDeprovisionCount(@NonNull @SatelliteManager.SatelliteError int result) {
+    public void reportDeprovisionCount(@NonNull @SatelliteManager.SatelliteResult int result) {
         SatelliteStats.SatelliteControllerParams controllerParam;
-        if (result == SatelliteManager.SATELLITE_ERROR_NONE) {
+        if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
             controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
                     .setCountOfDeprovisionSuccess(ADD_COUNT)
                     .build();
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
index 38696aa..d48c488 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
@@ -58,7 +58,7 @@
     }
 
     /** Sets the resultCode for provision metrics */
-    public ProvisionMetricsStats setResultCode(@SatelliteManager.SatelliteError int error) {
+    public ProvisionMetricsStats setResultCode(@SatelliteManager.SatelliteResult int error) {
         mResultCode = error;
         return this;
     }
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
index 776ba64..6585bec 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
@@ -30,7 +30,7 @@
     private static final boolean DBG = false;
 
     private static SessionMetricsStats sInstance = null;
-    private @SatelliteManager.SatelliteError int mInitializationResult;
+    private @SatelliteManager.SatelliteResult int mInitializationResult;
     private @SatelliteManager.NTRadioTechnology int mRadioTechnology;
 
     private SessionMetricsStats() {
@@ -54,7 +54,7 @@
 
     /** Sets the satellite initialization result */
     public SessionMetricsStats setInitializationResult(
-            @SatelliteManager.SatelliteError int result) {
+            @SatelliteManager.SatelliteResult int result) {
         logd("setInitializationResult(" + result + ")");
         mInitializationResult = result;
         return this;
@@ -81,7 +81,7 @@
     }
 
     private void initializeSessionMetricsParam() {
-        mInitializationResult = SatelliteManager.SATELLITE_ERROR_NONE;
+        mInitializationResult = SatelliteManager.SATELLITE_RESULT_SUCCESS;
         mRadioTechnology = SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN;
     }
 
diff --git a/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
new file mode 100644
index 0000000..4540b8a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.security;
+
+import android.content.Context;
+import android.telephony.CellularIdentifierDisclosure;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Encapsulates logic to emit notifications to the user that their cellular identifiers were
+ * disclosed in the clear. Callers add CellularIdentifierDisclosure instances by calling
+ * addDisclosure.
+ *
+ * <p>This class is thread safe and is designed to do costly work on worker threads. The intention
+ * is to allow callers to add disclosures from a Looper thread without worrying about blocking for
+ * IPC.
+ *
+ * @hide
+ */
+public class CellularIdentifierDisclosureNotifier {
+
+    private static final String TAG = "CellularIdentifierDisclosureNotifier";
+    private static final long DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES = 15;
+    private static CellularIdentifierDisclosureNotifier sInstance = null;
+    private final long mWindowCloseDuration;
+    private final TimeUnit mWindowCloseUnit;
+    private final CellularNetworkSecuritySafetySource mSafetySource;
+    private final Object mEnabledLock = new Object();
+
+    @GuardedBy("mEnabledLock")
+    private boolean mEnabled = false;
+    // This is a single threaded executor. This is important because we want to ensure certain
+    // events are strictly serialized.
+    private ScheduledExecutorService mSerializedWorkQueue;
+
+    // This object should only be accessed from within the thread of mSerializedWorkQueue. Access
+    // outside of that thread would require additional synchronization.
+    private Map<Integer, DisclosureWindow> mWindows;
+
+    public CellularIdentifierDisclosureNotifier(CellularNetworkSecuritySafetySource safetySource) {
+        this(
+                Executors.newSingleThreadScheduledExecutor(),
+                DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
+                TimeUnit.MINUTES,
+                safetySource);
+    }
+
+    /**
+     * Construct a CellularIdentifierDisclosureNotifier by injection. This should only be used for
+     * testing.
+     *
+     * @param notificationQueue a ScheduledExecutorService that should only execute on a single
+     *     thread.
+     */
+    @VisibleForTesting
+    public CellularIdentifierDisclosureNotifier(
+            ScheduledExecutorService notificationQueue,
+            long windowCloseDuration,
+            TimeUnit windowCloseUnit,
+            CellularNetworkSecuritySafetySource safetySource) {
+        mSerializedWorkQueue = notificationQueue;
+        mWindowCloseDuration = windowCloseDuration;
+        mWindowCloseUnit = windowCloseUnit;
+        mWindows = new HashMap<>();
+        mSafetySource = safetySource;
+    }
+
+    /**
+     * Add a CellularIdentifierDisclosure to be tracked by this instance. If appropriate, this will
+     * trigger a user notification.
+     */
+    public void addDisclosure(Context context, int subId, CellularIdentifierDisclosure disclosure) {
+        Rlog.d(TAG, "Identifier disclosure reported: " + disclosure);
+
+        synchronized (mEnabledLock) {
+            if (!mEnabled) {
+                Rlog.d(TAG, "Skipping disclosure because notifier was disabled.");
+                return;
+            }
+
+            // Don't notify if this disclosure happened in service of an emergency. That's a user
+            // initiated action that we don't want to interfere with.
+            if (disclosure.isEmergency()) {
+                Rlog.i(TAG, "Ignoring identifier disclosure associated with an emergency.");
+                return;
+            }
+
+            // Schedule incrementAndNotify from within the lock because we're sure at this point
+            // that we're enabled. This allows incrementAndNotify to avoid re-checking mEnabled
+            // because we know that any actions taken on disabled will be scheduled after this
+            // incrementAndNotify call.
+            try {
+                mSerializedWorkQueue.execute(incrementAndNotify(context, subId));
+            } catch (RejectedExecutionException e) {
+                Rlog.e(TAG, "Failed to schedule incrementAndNotify: " + e.getMessage());
+            }
+        } // end mEnabledLock
+    }
+
+    /**
+     * Re-enable if previously disabled. This means that {@code addDisclsoure} will start tracking
+     * disclosures again and potentially emitting notifications.
+     */
+    public void enable(Context context) {
+        synchronized (mEnabledLock) {
+            Rlog.d(TAG, "enabled");
+            mEnabled = true;
+            try {
+                mSerializedWorkQueue.execute(onEnableNotifier(context));
+            } catch (RejectedExecutionException e) {
+                Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Clear all internal state and prevent further notifications until optionally re-enabled.
+     * This can be used to in response to a user disabling the feature to emit notifications.
+     * If {@code addDisclosure} is called while in a disabled state, disclosures will be dropped.
+     */
+    public void disable(Context context) {
+        Rlog.d(TAG, "disabled");
+        synchronized (mEnabledLock) {
+            mEnabled = false;
+            try {
+                mSerializedWorkQueue.execute(onDisableNotifier(context));
+            } catch (RejectedExecutionException e) {
+                Rlog.e(TAG, "Failed to schedule onDisableNotifier: " + e.getMessage());
+            }
+        }
+    }
+
+    public boolean isEnabled() {
+        synchronized (mEnabledLock) {
+            return mEnabled;
+        }
+    }
+
+    /** Get a singleton CellularIdentifierDisclosureNotifier. */
+    public static synchronized CellularIdentifierDisclosureNotifier getInstance(
+            CellularNetworkSecuritySafetySource safetySource) {
+        if (sInstance == null) {
+            sInstance = new CellularIdentifierDisclosureNotifier(safetySource);
+        }
+
+        return sInstance;
+    }
+
+    private Runnable incrementAndNotify(Context context, int subId) {
+        return () -> {
+            DisclosureWindow window = mWindows.get(subId);
+            if (window == null) {
+                window = new DisclosureWindow(subId);
+                mWindows.put(subId, window);
+            }
+
+            window.increment(context, this);
+
+            int disclosureCount = window.getDisclosureCount();
+
+            Rlog.d(
+                    TAG,
+                    "Emitting notification for subId: "
+                            + subId
+                            + ". New disclosure count "
+                            + disclosureCount);
+
+            mSafetySource.setIdentifierDisclosure(
+                    context,
+                    subId,
+                    disclosureCount,
+                    window.getFirstOpen(),
+                    window.getCurrentEnd());
+        };
+    }
+
+    private Runnable onDisableNotifier(Context context) {
+        return () -> {
+            Rlog.d(TAG, "On disable notifier");
+            for (DisclosureWindow window : mWindows.values()) {
+                window.close();
+            }
+            mSafetySource.setIdentifierDisclosureIssueEnabled(context, false);
+        };
+    }
+
+    private Runnable onEnableNotifier(Context context) {
+        return () -> {
+            Rlog.i(TAG, "On enable notifier");
+            mSafetySource.setIdentifierDisclosureIssueEnabled(context, true);
+        };
+    }
+
+    /**
+     * Get the disclosure count for a given subId. NOTE: This method is not thread safe. Without
+     * external synchronization, one should only call it if there are no pending tasks on the
+     * Executor passed into this class.
+     */
+    @VisibleForTesting
+    public int getCurrentDisclosureCount(int subId) {
+        DisclosureWindow window = mWindows.get(subId);
+        if (window != null) {
+            return window.getDisclosureCount();
+        }
+
+        return 0;
+    }
+
+    /**
+     * Get the open time for a given subId. NOTE: This method is not thread safe. Without
+     * external synchronization, one should only call it if there are no pending tasks on the
+     * Executor passed into this class.
+     */
+    @VisibleForTesting
+    public Instant getFirstOpen(int subId) {
+        DisclosureWindow window = mWindows.get(subId);
+        if (window != null) {
+            return window.getFirstOpen();
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the current end time for a given subId. NOTE: This method is not thread safe. Without
+     * external synchronization, one should only call it if there are no pending tasks on the
+     * Executor passed into this class.
+     */
+    @VisibleForTesting
+    public Instant getCurrentEnd(int subId) {
+        DisclosureWindow window = mWindows.get(subId);
+        if (window != null) {
+            return window.getCurrentEnd();
+        }
+
+        return null;
+    }
+
+    /**
+     * A helper class that maintains all state associated with the disclosure window for a single
+     * subId. No methods are thread safe. Callers must implement all synchronization.
+     */
+    private class DisclosureWindow {
+        private int mDisclosureCount;
+        private Instant mWindowFirstOpen;
+        private Instant mLastEvent;
+        private ScheduledFuture<?> mWhenWindowCloses;
+
+        private int mSubId;
+
+        DisclosureWindow(int subId) {
+            mDisclosureCount = 0;
+            mWindowFirstOpen = null;
+            mLastEvent = null;
+            mSubId = subId;
+            mWhenWindowCloses = null;
+        }
+
+        void increment(Context context, CellularIdentifierDisclosureNotifier notifier) {
+
+            mDisclosureCount++;
+
+            Instant now = Instant.now();
+            if (mDisclosureCount == 1) {
+                // Our window was opened for the first time
+                mWindowFirstOpen = now;
+            }
+
+            mLastEvent = now;
+
+            cancelWindowCloseFuture();
+
+            try {
+                mWhenWindowCloses =
+                        notifier.mSerializedWorkQueue.schedule(
+                                closeWindowRunnable(context),
+                                notifier.mWindowCloseDuration,
+                                notifier.mWindowCloseUnit);
+            } catch (RejectedExecutionException e) {
+                Rlog.e(
+                        TAG,
+                        "Failed to schedule closeWindow for subId "
+                                + mSubId
+                                + " :  "
+                                + e.getMessage());
+            }
+        }
+
+        int getDisclosureCount() {
+            return mDisclosureCount;
+        }
+
+        Instant getFirstOpen() {
+            return mWindowFirstOpen;
+        }
+
+        Instant getCurrentEnd() {
+            return mLastEvent;
+        }
+
+        void close() {
+            mDisclosureCount = 0;
+            mWindowFirstOpen = null;
+            mLastEvent = null;
+
+            if (mWhenWindowCloses == null) {
+                return;
+            }
+            mWhenWindowCloses = null;
+        }
+
+        private Runnable closeWindowRunnable(Context context) {
+            return () -> {
+                Rlog.i(
+                        TAG,
+                        "Disclosure window closing for subId "
+                                + mSubId
+                                + ". Disclosure count was "
+                                + getDisclosureCount());
+                close();
+                mSafetySource.clearIdentifierDisclosure(context, mSubId);
+            };
+        }
+
+        private boolean cancelWindowCloseFuture() {
+            if (mWhenWindowCloses == null) {
+                return false;
+            }
+
+            // Pass false to not interrupt a running Future. Nothing about our notifier is ready
+            // for this type of preemption.
+            return mWhenWindowCloses.cancel(false);
+        }
+
+    }
+}
+
diff --git a/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
new file mode 100644
index 0000000..34f26e3
--- /dev/null
+++ b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.security;
+
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED;
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED;
+import static android.safetycenter.SafetySourceData.SEVERITY_LEVEL_INFORMATION;
+import static android.safetycenter.SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION;
+
+import android.annotation.IntDef;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.safetycenter.SafetyCenterManager;
+import android.safetycenter.SafetyEvent;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceIssue;
+import android.safetycenter.SafetySourceStatus;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * Holds the state needed to report the Safety Center status and issues related to cellular
+ * network security.
+ */
+public class CellularNetworkSecuritySafetySource {
+    private static final String SAFETY_SOURCE_ID = "AndroidCellularNetworkSecurity";
+
+    private static final String NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID = "null_cipher_non_encrypted";
+    private static final String NULL_CIPHER_ISSUE_ENCRYPTED_ID = "null_cipher_encrypted";
+
+    private static final String NULL_CIPHER_ACTION_SETTINGS_ID = "cellular_security_settings";
+    private static final String NULL_CIPHER_ACTION_LEARN_MORE_ID = "learn_more";
+
+    private static final String IDENTIFIER_DISCLOSURE_ISSUE_ID = "identifier_disclosure";
+
+    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;
+
+    @IntDef(
+        prefix = {"NULL_CIPHER_STATE_"},
+        value = {
+            NULL_CIPHER_STATE_ENCRYPTED,
+            NULL_CIPHER_STATE_NOTIFY_ENCRYPTED,
+            NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface NullCipherState {}
+
+    private static CellularNetworkSecuritySafetySource sInstance;
+
+    private final SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+    private final SubscriptionManagerService mSubscriptionManagerService;
+
+    private boolean mNullCipherStateIssuesEnabled;
+    private HashMap<Integer, Integer> mNullCipherStates = new HashMap<>();
+
+    private boolean mIdentifierDisclosureIssuesEnabled;
+    private HashMap<Integer, IdentifierDisclosure> mIdentifierDisclosures = new HashMap<>();
+
+    /**
+     * Gets a singleton CellularNetworkSecuritySafetySource.
+     */
+    public static synchronized CellularNetworkSecuritySafetySource getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new CellularNetworkSecuritySafetySource(
+                    new SafetyCenterManagerWrapper(context));
+        }
+        return sInstance;
+    }
+
+    @VisibleForTesting
+    public CellularNetworkSecuritySafetySource(
+            SafetyCenterManagerWrapper safetyCenterManagerWrapper) {
+        mSafetyCenterManagerWrapper = safetyCenterManagerWrapper;
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+    }
+
+    /** Enables or disables the null cipher issue and clears any current issues. */
+    public synchronized void setNullCipherIssueEnabled(Context context, boolean enabled) {
+        mNullCipherStateIssuesEnabled = enabled;
+        mNullCipherStates.clear();
+        updateSafetyCenter(context);
+    }
+
+    /** Sets the null cipher issue state for the identified subscription. */
+    public synchronized void setNullCipherState(
+            Context context, int subId, @NullCipherState int nullCipherState) {
+        mNullCipherStates.put(subId, nullCipherState);
+        updateSafetyCenter(context);
+    }
+
+    /**
+     * Enables or disables the identifier disclosure issue and clears any current issues if the
+     * enable state is changed.
+     */
+    public synchronized void setIdentifierDisclosureIssueEnabled(Context context, boolean enabled) {
+        // This check ensures that if we're enabled and we are asked to enable ourselves again (can
+        // happen if the modem restarts), we don't clear our state.
+        if (enabled != mIdentifierDisclosureIssuesEnabled) {
+            mIdentifierDisclosureIssuesEnabled = enabled;
+            mIdentifierDisclosures.clear();
+            updateSafetyCenter(context);
+        }
+    }
+
+    /** Sets the identifier disclosure issue state for the identifier subscription. */
+    public synchronized void setIdentifierDisclosure(
+            Context context, int subId, int count, Instant start, Instant end) {
+        IdentifierDisclosure disclosure = new IdentifierDisclosure(count, start, end);
+        mIdentifierDisclosures.put(subId, disclosure);
+        updateSafetyCenter(context);
+    }
+
+    /** Clears the identifier disclosure issue state for the identified subscription. */
+    public synchronized void clearIdentifierDisclosure(Context context, int subId) {
+        mIdentifierDisclosures.remove(subId);
+        updateSafetyCenter(context);
+    }
+
+    /** Refreshed the safety source in response to the identified broadcast. */
+    public synchronized void refresh(Context context, String refreshBroadcastId) {
+        mSafetyCenterManagerWrapper.setRefreshedSafetySourceData(
+                refreshBroadcastId, getSafetySourceData(context));
+    }
+
+    private void updateSafetyCenter(Context context) {
+        mSafetyCenterManagerWrapper.setSafetySourceData(getSafetySourceData(context));
+    }
+
+    private boolean isSafetySourceHidden() {
+        return !mNullCipherStateIssuesEnabled && !mIdentifierDisclosureIssuesEnabled;
+    }
+
+    private SafetySourceData getSafetySourceData(Context context) {
+        if (isSafetySourceHidden()) {
+            // The cellular network security safety source is configured with
+            // initialDisplayState="hidden"
+            return null;
+        }
+
+        Stream<Optional<SafetySourceIssue>> nullCipherIssues =
+                mNullCipherStates.entrySet().stream()
+                        .map(e -> getNullCipherIssue(context, e.getKey(), e.getValue()));
+        Stream<Optional<SafetySourceIssue>> identifierDisclosureIssues =
+                mIdentifierDisclosures.entrySet().stream()
+                        .map(e -> getIdentifierDisclosureIssue(context, e.getKey(), e.getValue()));
+        SafetySourceIssue[] issues = Stream.concat(nullCipherIssues, identifierDisclosureIssues)
+                .flatMap(Optional::stream)
+                .toArray(SafetySourceIssue[]::new);
+
+        SafetySourceData.Builder builder = new SafetySourceData.Builder();
+        int maxSeverity = SEVERITY_LEVEL_INFORMATION;
+        for (SafetySourceIssue issue : issues) {
+            builder.addIssue(issue);
+            maxSeverity = Math.max(maxSeverity, issue.getSeverityLevel());
+        }
+
+        builder.setStatus(
+                new SafetySourceStatus.Builder(
+                        context.getString(R.string.scCellularNetworkSecurityTitle),
+                        context.getString(R.string.scCellularNetworkSecuritySummary),
+                        maxSeverity)
+                    .setPendingIntent(mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                            context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
+                    .build());
+        return builder.build();
+    }
+
+    /** Builds the null cipher issue if it's enabled and there are null ciphers to report. */
+    private Optional<SafetySourceIssue> getNullCipherIssue(
+            Context context, int subId, @NullCipherState int state) {
+        if (!mNullCipherStateIssuesEnabled) {
+            return Optional.empty();
+        }
+
+        SubscriptionInfoInternal subInfo =
+                mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
+        final SafetySourceIssue.Builder builder;
+        switch (state) {
+            case NULL_CIPHER_STATE_ENCRYPTED:
+                return Optional.empty();
+            case NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED:
+                builder = new SafetySourceIssue.Builder(
+                        NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID + "_" + subId,
+                        context.getString(
+                            R.string.scNullCipherIssueNonEncryptedTitle, subInfo.getDisplayName()),
+                        context.getString(R.string.scNullCipherIssueNonEncryptedSummary),
+                        SEVERITY_LEVEL_RECOMMENDATION,
+                        NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID);
+                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),
+                        SEVERITY_LEVEL_INFORMATION,
+                        NULL_CIPHER_ISSUE_ENCRYPTED_ID);
+                break;
+            default:
+                throw new AssertionError();
+        }
+
+        return Optional.of(
+                builder
+                    .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());
+    }
+
+    /** Builds the identity disclosure issue if it's enabled and there are disclosures to report. */
+    private Optional<SafetySourceIssue> getIdentifierDisclosureIssue(
+            Context context, int subId, IdentifierDisclosure disclosure) {
+        if (!mIdentifierDisclosureIssuesEnabled || disclosure.getDisclosureCount() == 0) {
+            return Optional.empty();
+        }
+
+        SubscriptionInfoInternal subInfo =
+                mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
+        return Optional.of(
+                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()),
+                                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());
+    }
+
+    /** A wrapper around {@link SafetyCenterManager} that can be instrumented in tests. */
+    @VisibleForTesting
+    public static class SafetyCenterManagerWrapper {
+        private final SafetyCenterManager mSafetyCenterManager;
+
+        public SafetyCenterManagerWrapper(Context context) {
+            mSafetyCenterManager = context.getSystemService(SafetyCenterManager.class);
+        }
+
+        /** Retrieve a {@link PendingIntent} that will start a new activity. */
+        public PendingIntent getActivityPendingIntent(Context context, Intent intent) {
+            return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+        }
+
+        /** Set the {@link SafetySourceData} for this safety source. */
+        public void setSafetySourceData(SafetySourceData safetySourceData) {
+            mSafetyCenterManager.setSafetySourceData(
+                    SAFETY_SOURCE_ID,
+                    safetySourceData,
+                    new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build());
+        }
+
+        /** Sets the {@link SafetySourceData} in response to a refresh request. */
+        public void setRefreshedSafetySourceData(
+                String refreshBroadcastId, SafetySourceData safetySourceData) {
+            mSafetyCenterManager.setSafetySourceData(
+                    SAFETY_SOURCE_ID,
+                    safetySourceData,
+                    new SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+                            .setRefreshBroadcastId(refreshBroadcastId)
+                            .build());
+        }
+    }
+
+    private static class IdentifierDisclosure {
+        private final int mDisclosureCount;
+        private final Instant mWindowStart;
+        private final Instant mWindowEnd;
+
+        private IdentifierDisclosure(int count, Instant start, Instant end) {
+            mDisclosureCount = count;
+            mWindowStart = start;
+            mWindowEnd  = end;
+        }
+
+        private int getDisclosureCount() {
+            return mDisclosureCount;
+        }
+
+        private Instant getWindowStart() {
+            return mWindowStart;
+        }
+
+        private Instant getWindowEnd() {
+            return mWindowEnd;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof IdentifierDisclosure)) {
+                return false;
+            }
+            IdentifierDisclosure other = (IdentifierDisclosure) o;
+            return mDisclosureCount == other.mDisclosureCount
+                    && Objects.equals(mWindowStart, other.mWindowStart)
+                    && Objects.equals(mWindowEnd, other.mWindowEnd);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mDisclosureCount, mWindowStart, mWindowEnd);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/security/NullCipherNotifier.java b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
new file mode 100644
index 0000000..0ab8299
--- /dev/null
+++ b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.security;
+
+import android.telephony.SecurityAlgorithmUpdate;
+
+import com.android.telephony.Rlog;
+
+/**
+ * Encapsulates logic to emit notifications to the user that a null cipher is in use. A null cipher
+ * is one that does not attempt to implement any encryption.
+ *
+ * <p>This class will either emit notifications through SafetyCenterManager if SafetyCenter exists
+ * on a device, or it will emit system notifications otherwise.
+ *
+ * @hide
+ */
+public class NullCipherNotifier {
+
+    private static final String TAG = "NullCipherNotifier";
+    private static NullCipherNotifier sInstance;
+
+    private boolean mEnabled = false;
+
+    /**
+     * Gets a singleton NullCipherNotifier.
+     */
+    public static synchronized NullCipherNotifier getInstance() {
+        if (sInstance == null) {
+            sInstance = new NullCipherNotifier();
+        }
+        return sInstance;
+    }
+
+    private NullCipherNotifier() {}
+
+    /**
+     * Adds a security algorithm update. If appropriate, this will trigger a user notification.
+     */
+    public void onSecurityAlgorithmUpdate(int phoneId, SecurityAlgorithmUpdate update) {
+        // TODO (b/315005938) this is a stub method for now. Logic
+        // for tracking disclosures and emitting notifications will flow
+        // from here.
+        Rlog.d(TAG, "Security algorithm update: phoneId = " + phoneId + " " + update);
+    }
+
+    /**
+     * Enables null cipher notification; {@code onSecurityAlgorithmUpdate} will start handling
+     * security algorithm updates and send notifications to the user when required.
+     */
+    public void enable() {
+        Rlog.d(TAG, "enabled");
+        mEnabled = true;
+    }
+
+    /**
+     * Clear all internal state and prevent further notifications until re-enabled. This can be
+     * used in response to a user disabling the feature for null cipher notifications. If
+     * {@code onSecurityAlgorithmUpdate} is called while in a disabled state, security algorithm
+     * updates will be dropped.
+     */
+    public void disable() {
+        Rlog.d(TAG, "disabled");
+        mEnabled = false;
+    }
+
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
index 124437c..3d07d47 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -48,6 +48,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.util.function.TriConsumer;
 import com.android.telephony.Rlog;
@@ -274,7 +275,25 @@
                     SubscriptionInfoInternal::getUserId),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SATELLITE_ENABLED,
-                    SubscriptionInfoInternal::getSatelliteEnabled)
+                    SubscriptionInfoInternal::getSatelliteEnabled),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
+                    SubscriptionInfoInternal::getSatelliteAttachEnabledForCarrier),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_IS_NTN,
+                    SubscriptionInfoInternal::getOnlyNonTerrestrialNetwork),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SERVICE_CAPABILITIES,
+                    SubscriptionInfoInternal::getServiceCapabilities),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_TRANSFER_STATUS,
+                    SubscriptionInfoInternal::getTransferStatus),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+                    SubscriptionInfoInternal::getSatelliteEntitlementStatus),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+                    SubscriptionInfoInternal::getSatelliteEntitlementPlmns)
     );
 
     /**
@@ -399,7 +418,22 @@
                     SubscriptionDatabaseManager::setUserId),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SATELLITE_ENABLED,
-                    SubscriptionDatabaseManager::setSatelliteEnabled)
+                    SubscriptionDatabaseManager::setSatelliteEnabled),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
+                    SubscriptionDatabaseManager::setSatelliteAttachEnabledForCarrier),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_IS_NTN,
+                    SubscriptionDatabaseManager::setNtn),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SERVICE_CAPABILITIES,
+                    SubscriptionDatabaseManager::setServiceCapabilities),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_TRANSFER_STATUS,
+                    SubscriptionDatabaseManager::setTransferStatus),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+                    SubscriptionDatabaseManager::setSatelliteEntitlementStatus)
     );
 
     /**
@@ -461,7 +495,10 @@
                     SubscriptionDatabaseManager::setNumberFromCarrier),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS,
-                    SubscriptionDatabaseManager::setNumberFromIms)
+                    SubscriptionDatabaseManager::setNumberFromIms),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+                    SubscriptionDatabaseManager::setSatelliteEntitlementPlmns)
     );
 
     /**
@@ -508,7 +545,9 @@
             SimInfo.COLUMN_VOIMS_OPT_IN_STATUS,
             SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS,
             SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
-            SimInfo.COLUMN_USER_HANDLE
+            SimInfo.COLUMN_USER_HANDLE,
+            SimInfo.COLUMN_SATELLITE_ENABLED,
+            SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER
     );
 
     /**
@@ -529,6 +568,10 @@
     @NonNull
     private final Context mContext;
 
+    /** The feature flags */
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
     /** The callback used for passing events back to {@link SubscriptionManagerService}. */
     @NonNull
     private final SubscriptionDatabaseManagerCallback mCallback;
@@ -619,9 +662,11 @@
      *
      * @param context The context.
      * @param looper Looper for the handler.
+     * @param featureFlags The feature flags.
      * @param callback Subscription database callback.
      */
     public SubscriptionDatabaseManager(@NonNull Context context, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
             @NonNull SubscriptionDatabaseManagerCallback callback) {
         super(looper);
         log("Created SubscriptionDatabaseManager.");
@@ -630,6 +675,7 @@
         mUiccController = UiccController.getInstance();
         mAsyncMode = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_subscription_database_async_update);
+        mFeatureFlags = featureFlags;
         initializeDatabase();
     }
 
@@ -1988,6 +2034,40 @@
     }
 
     /**
+     * Set whether satellite attach for carrier is enabled or disabled by user.
+     *
+     * @param subId Subscription id.
+     * @param isSatelliteAttachEnabledForCarrier Whether satellite attach for carrier is enabled or
+     * disabled.
+     *
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setSatelliteAttachEnabledForCarrier(int subId,
+            int isSatelliteAttachEnabledForCarrier) {
+        writeDatabaseAndCacheHelper(subId,
+                SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
+                isSatelliteAttachEnabledForCarrier,
+                SubscriptionInfoInternal.Builder::setSatelliteAttachEnabledForCarrier);
+    }
+
+    /**
+     * Set whether the subscription is exclusively used for non-terrestrial networks or not.
+     *
+     * @param subId Subscription ID.
+     * @param isNtn {@code 1} if it is a non-terrestrial network subscription.
+     * {@code 0} otherwise.
+     *
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setNtn(int subId, int isNtn) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            return;
+        }
+        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_IS_NTN, isNtn,
+                SubscriptionInfoInternal.Builder::setOnlyNonTerrestrialNetwork);
+    }
+
+    /**
      * Set whether group of the subscription is disabled. This is only useful if it's a grouped
      * opportunistic subscription. In this case, if all primary (non-opportunistic)
      * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
@@ -2000,7 +2080,7 @@
      */
     public void setGroupDisabled(int subId, boolean isGroupDisabled) {
         // group disabled does not have a corresponding SimInfo column. So we only update the cache.
-
+        boolean isChanged = false;
         // Grab the write lock so no other threads can read or write the cache.
         mReadWriteLock.writeLock().lock();
         try {
@@ -2009,12 +2089,76 @@
                 throw new IllegalArgumentException("setGroupDisabled: Subscription doesn't exist. "
                         + "subId=" + subId);
             }
+            isChanged = subInfoCache.isGroupDisabled() != isGroupDisabled;
             mAllSubscriptionInfoInternalCache.put(subId,
                     new SubscriptionInfoInternal.Builder(subInfoCache)
                             .setGroupDisabled(isGroupDisabled).build());
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
+
+        if (isChanged) {
+            log("setGroupDisabled value changed, firing the callback");
+            mCallback.invokeFromExecutor(() -> mCallback.onSubscriptionChanged(subId));
+        }
+    }
+
+    /**
+     * Set service capabilities the subscription support.
+     * @param subId Subscription id.
+     * @param capabilities Service capabilities bitmasks
+     */
+    public void setServiceCapabilities(int subId, int capabilities) {
+        if (!mFeatureFlags.dataOnlyCellularService()) {
+            return;
+        }
+        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_SERVICE_CAPABILITIES,
+                capabilities, SubscriptionInfoInternal.Builder::setServiceCapabilities);
+    }
+
+    /**
+     * Set whether satellite entitlement status is enabled by entitlement query result.
+     *
+     * @param subId Subscription id.
+     * @param isSatelliteEntitlementStatus Whether satellite entitlement status is enabled or
+     * disabled.
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setSatelliteEntitlementStatus(int subId,
+            int isSatelliteEntitlementStatus) {
+        writeDatabaseAndCacheHelper(subId,
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+                isSatelliteEntitlementStatus,
+                SubscriptionInfoInternal.Builder::setSatelliteEntitlementStatus);
+    }
+
+    /**
+     * Set satellite entitlement plmns by entitlement query result.
+     *
+     * @param subId Subscription id.
+     * @param satelliteEntitlementPlmns Satellite entitlement plmns
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setSatelliteEntitlementPlmns(int subId,
+            @NonNull String satelliteEntitlementPlmns) {
+        writeDatabaseAndCacheHelper(subId,
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+                satelliteEntitlementPlmns,
+                SubscriptionInfoInternal.Builder::setSatelliteEntitlementPlmns);
+    }
+
+    /**
+     * Set satellite entitlement plmn list by entitlement query result.
+     *
+     * @param subId Subscription id.
+     * @param satelliteEntitlementPlmnList Satellite entitlement plmn list
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setSatelliteEntitlementPlmnList(int subId,
+            @NonNull List<String> satelliteEntitlementPlmnList) {
+        String satelliteEntitlementPlmns = satelliteEntitlementPlmnList.stream().collect(
+                Collectors.joining(","));
+        setSatelliteEntitlementPlmns(subId, satelliteEntitlementPlmns);
     }
 
     /**
@@ -2243,7 +2387,27 @@
                 .setUserId(cursor.getInt(cursor.getColumnIndexOrThrow(
                         SimInfo.COLUMN_USER_HANDLE)))
                 .setSatelliteEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SimInfo.COLUMN_SATELLITE_ENABLED)));
+                        SimInfo.COLUMN_SATELLITE_ENABLED)))
+                .setSatelliteAttachEnabledForCarrier(cursor.getInt(
+                        cursor.getColumnIndexOrThrow(
+                                SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER)))
+                .setServiceCapabilities(cursor.getInt(
+                        cursor.getColumnIndexOrThrow(
+                                SimInfo.COLUMN_SERVICE_CAPABILITIES)))
+                .setSatelliteEntitlementStatus(cursor.getInt(
+                        cursor.getColumnIndexOrThrow(
+                                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS)))
+                .setSatelliteEntitlementPlmns(cursor.getString(
+                        cursor.getColumnIndexOrThrow(
+                                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS)));
+        if (mFeatureFlags.oemEnabledSatelliteFlag()) {
+            builder.setOnlyNonTerrestrialNetwork(cursor.getInt(cursor.getColumnIndexOrThrow(
+                    SimInfo.COLUMN_IS_NTN)));
+        }
+        if (mFeatureFlags.supportPsimToEsimConversion()) {
+            builder.setTransferStatus(cursor.getInt(cursor.getColumnIndexOrThrow(
+                    SimInfo.COLUMN_TRANSFER_STATUS)));
+        }
         return builder.build();
     }
 
@@ -2318,6 +2482,25 @@
     }
 
     /**
+     * Set the transfer status of the subscriptionInfo that corresponds to subId.
+     *
+     * @param subId Subscription ID.
+     * @param status The transfer status to change.
+     *
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setTransferStatus(int subId, int status) {
+        if (!mFeatureFlags.supportPsimToEsimConversion()) {
+            log("SubscriptionDatabaseManager:supportPsimToEsimConversion is false");
+            return;
+        }
+
+        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_TRANSFER_STATUS,
+                status,
+                SubscriptionInfoInternal.Builder::setTransferStatus);
+    }
+
+    /**
      * Log debug messages.
      *
      * @param s debug messages
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
index b917698..a2ebf4e 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
@@ -442,6 +442,18 @@
      */
     private final int mIsSatelliteEnabled;
 
+    /**
+     * Whether satellite attach for carrier is enabled or disabled by user.
+     * By default, its enabled. It is intended to use integer to fit the database format.
+     */
+    private final int mIsSatelliteAttachEnabledForCarrier;
+
+    /**
+     * Whether this subscription is used for communicating with non-terrestrial networks.
+     * By default, its disabled. It is intended to use integer to fit the database format.
+     */
+    private final int mIsOnlyNonTerrestrialNetwork;
+
     // Below are the fields that do not exist in the SimInfo table.
     /**
      * The card ID of the SIM card. This maps uniquely to {@link #mCardString}.
@@ -457,6 +469,28 @@
     private final boolean mIsGroupDisabled;
 
     /**
+     * Service capabilities (in the form of bitmask combination) the subscription supports.
+     */
+    private final int mServiceCapabilities;
+
+    /**
+     * The transfer status of the subscription
+     */
+    private final int mTransferStatus;
+
+    /**
+     * Whether satellite entitlement status is enabled or disabled by the entitlement query result.
+     * By default, its disabled. It is intended to use integer to fit the database format.
+     */
+    private final int mIsSatelliteEntitlementStatus;
+
+    /**
+     * The satellite entitlement plmns based on the entitlement query results
+     * By default, its empty. It is intended to use string to fit the database format.
+     */
+    @NonNull private final String mSatelliteEntitlementPlmns;
+
+    /**
      * Constructor from builder.
      *
      * @param builder Builder of {@link SubscriptionInfoInternal}.
@@ -524,10 +558,17 @@
         this.mLastUsedTPMessageReference = builder.mLastUsedTPMessageReference;
         this.mUserId = builder.mUserId;
         this.mIsSatelliteEnabled = builder.mIsSatelliteEnabled;
+        this.mIsSatelliteAttachEnabledForCarrier =
+                builder.mIsSatelliteAttachEnabledForCarrier;
+        this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork;
 
         // Below are the fields that do not exist in the SimInfo table.
         this.mCardId = builder.mCardId;
         this.mIsGroupDisabled = builder.mIsGroupDisabled;
+        this.mServiceCapabilities = builder.mServiceCapabilities;
+        this.mTransferStatus = builder.mTransferStatus;
+        this.mIsSatelliteEntitlementStatus = builder.mIsSatelliteEntitlementStatus;
+        this.mSatelliteEntitlementPlmns = builder.mSatelliteEntitlementPlmns;
     }
 
     /**
@@ -1128,6 +1169,22 @@
         return mIsSatelliteEnabled;
     }
 
+    /**
+     * @return {@code 1} if satellite attach for carrier is enabled by user.
+     */
+    public int getSatelliteAttachEnabledForCarrier() {
+        return mIsSatelliteAttachEnabledForCarrier;
+    }
+
+    /**
+     * An NTN subscription connects to non-terrestrial networks.
+     *
+     * @return {@code 1} if the subscription is for non-terrestrial networks. {@code 0} otherwise.
+     */
+    public int getOnlyNonTerrestrialNetwork() {
+        return mIsOnlyNonTerrestrialNetwork;
+    }
+
     // Below are the fields that do not exist in SimInfo table.
     /**
      * @return The card ID of the SIM card which contains the subscription.
@@ -1162,6 +1219,36 @@
         return !isOpportunistic() || TextUtils.isEmpty(mGroupUuid);
     }
 
+    /**
+     * Return the service capabilities bitmasks the subscription supports.
+     */
+    public int getServiceCapabilities() {
+        return mServiceCapabilities;
+    }
+    /**
+     * @return Transfer status.
+     */
+    public int getTransferStatus() {
+        return mTransferStatus;
+    }
+
+    /**
+     * @return {@code 1} if satellite entitlement status is enabled by entitlement query result.
+     */
+    public int getSatelliteEntitlementStatus() {
+        return mIsSatelliteEntitlementStatus;
+    }
+
+    /**
+     * @return Satellite entitlement plmns is empty or not by entitlement query result.
+     *
+     * For example, "123123, 12310" or ""
+     */
+    @NonNull
+    public String getSatelliteEntitlementPlmns() {
+        return mSatelliteEntitlementPlmns;
+    }
+
     /** @return converted {@link SubscriptionInfo}. */
     @NonNull
     public SubscriptionInfo toSubscriptionInfo() {
@@ -1197,6 +1284,10 @@
                 .setUiccApplicationsEnabled(mAreUiccApplicationsEnabled != 0)
                 .setPortIndex(mPortIndex)
                 .setUsageSetting(mUsageSetting)
+                .setOnlyNonTerrestrialNetwork(mIsOnlyNonTerrestrialNetwork == 1)
+                .setServiceCapabilities(
+                        SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities))
+                .setTransferStatus(mTransferStatus)
                 .build();
     }
 
@@ -1253,7 +1344,13 @@
                 + " numberFromIms=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumberFromIms)
                 + " userId=" + mUserId
                 + " isSatelliteEnabled=" + mIsSatelliteEnabled
+                + " satellite_attach_enabled_for_carrier=" + mIsSatelliteAttachEnabledForCarrier
+                + " getOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
                 + " isGroupDisabled=" + mIsGroupDisabled
+                + " serviceCapabilities=" + mServiceCapabilities
+                + " transferStatus=" + mTransferStatus
+                + " satelliteEntitlementStatus=" + mIsSatelliteEntitlementStatus
+                + " satelliteEntitlementPlmns=" + mSatelliteEntitlementPlmns
                 + "]";
     }
 
@@ -1308,7 +1405,13 @@
                 mRcsConfig, that.mRcsConfig) && mAllowedNetworkTypesForReasons.equals(
                 that.mAllowedNetworkTypesForReasons) && mDeviceToDeviceStatusSharingContacts.equals(
                 that.mDeviceToDeviceStatusSharingContacts) && mNumberFromCarrier.equals(
-                that.mNumberFromCarrier) && mNumberFromIms.equals(that.mNumberFromIms);
+                that.mNumberFromCarrier) && mNumberFromIms.equals(that.mNumberFromIms)
+                && mIsSatelliteAttachEnabledForCarrier == that.mIsSatelliteAttachEnabledForCarrier
+                && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
+                && mServiceCapabilities == that.mServiceCapabilities
+                && mTransferStatus == that.mTransferStatus
+                && mIsSatelliteEntitlementStatus == that.mIsSatelliteEntitlementStatus
+                && mSatelliteEntitlementPlmns == that.mSatelliteEntitlementPlmns;
     }
 
     @Override
@@ -1329,7 +1432,10 @@
                 mDeviceToDeviceStatusSharingContacts, mIsNrAdvancedCallingEnabled,
                 mNumberFromCarrier,
                 mNumberFromIms, mPortIndex, mUsageSetting, mLastUsedTPMessageReference, mUserId,
-                mIsSatelliteEnabled, mCardId, mIsGroupDisabled);
+                mIsSatelliteEnabled, mCardId, mIsGroupDisabled,
+                mIsSatelliteAttachEnabledForCarrier, mIsOnlyNonTerrestrialNetwork,
+                mServiceCapabilities, mTransferStatus, mIsSatelliteEntitlementStatus,
+                mSatelliteEntitlementPlmns);
         result = 31 * result + Arrays.hashCode(mNativeAccessRules);
         result = 31 * result + Arrays.hashCode(mCarrierConfigAccessRules);
         result = 31 * result + Arrays.hashCode(mRcsConfig);
@@ -1690,7 +1796,17 @@
         /**
          * Whether satellite is enabled or not.
          */
-        private int mIsSatelliteEnabled = -1;
+        private int mIsSatelliteEnabled = 0;
+
+        /**
+         * Whether satellite attach for carrier is enabled by user.
+         */
+        private int mIsSatelliteAttachEnabledForCarrier = 1;
+
+        /**
+         * Whether this subscription is used for communicating with non-terrestrial network or not.
+         */
+        private int mIsOnlyNonTerrestrialNetwork = 0;
 
         // The following fields do not exist in the SimInfo table.
         /**
@@ -1707,6 +1823,27 @@
         private boolean mIsGroupDisabled;
 
         /**
+         * Service capabilities the subscription supports
+         */
+        private int mServiceCapabilities;
+
+        /**
+         * The transfer status of the subscription
+         */
+        private int mTransferStatus;
+
+        /**
+         * Whether satellite entitlement status is enabled by entitlement query result.
+         */
+        private int mIsSatelliteEntitlementStatus = 0;
+
+        /**
+         * Whether satellite entitlement plmns is empty or not by entitlement query result.
+         */
+        @NonNull
+        private String mSatelliteEntitlementPlmns = "";
+
+        /**
          * Default constructor.
          */
         public Builder() {
@@ -1779,9 +1916,15 @@
             mLastUsedTPMessageReference = info.getLastUsedTPMessageReference();
             mUserId = info.mUserId;
             mIsSatelliteEnabled = info.mIsSatelliteEnabled;
+            mIsSatelliteAttachEnabledForCarrier = info.mIsSatelliteAttachEnabledForCarrier;
+            mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork;
             // Below are the fields that do not exist in the SimInfo table.
             mCardId = info.mCardId;
             mIsGroupDisabled = info.mIsGroupDisabled;
+            mServiceCapabilities = info.mServiceCapabilities;
+            mTransferStatus = info.mTransferStatus;
+            mIsSatelliteEntitlementStatus = info.mIsSatelliteEntitlementStatus;
+            mSatelliteEntitlementPlmns = info.mSatelliteEntitlementPlmns;
         }
 
         /**
@@ -2649,6 +2792,32 @@
             return this;
         }
 
+        /**
+         * Set whether satellite attach for carrier is enabled or disabled by user.
+         * @param isSatelliteAttachEnabledForCarrier {@code 1} if satellite attach for carrier is
+         * enabled.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setSatelliteAttachEnabledForCarrier(
+                @NonNull int isSatelliteAttachEnabledForCarrier) {
+            mIsSatelliteAttachEnabledForCarrier = isSatelliteAttachEnabledForCarrier;
+            return this;
+        }
+
+        /**
+         * Set whether the subscription is for NTN or not.
+         *
+         * @param isOnlyNonTerrestrialNetwork {@code 1} if the subscription is for NTN, {@code 0}
+         * otherwise.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setOnlyNonTerrestrialNetwork(int isOnlyNonTerrestrialNetwork) {
+            mIsOnlyNonTerrestrialNetwork = isOnlyNonTerrestrialNetwork;
+            return this;
+        }
+
         // Below are the fields that do not exist in the SimInfo table.
         /**
          * Set the card ID of the SIM card which contains the subscription.
@@ -2678,6 +2847,54 @@
         }
 
         /**
+         * Set the service capabilities the subscription supports.
+         * @param capabilities Cellular service capabilities bitmasks
+         * @return The builder
+         */
+        public Builder setServiceCapabilities(int capabilities) {
+            mServiceCapabilities = capabilities;
+            return this;
+        }
+
+        /**
+         * Set the transfer status of the subscription.
+         *
+         * @param status The transfer status
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setTransferStatus(int status) {
+            mTransferStatus = status;
+            return this;
+        }
+
+        /**
+         * Set whether satellite entitlement status is enabled by entitlement query result.
+         *
+         * @param isSatelliteEntitlementStatus {@code 1} if satellite entitlement status is
+         * enabled by entitlement query result.
+         * @return The builder
+         */
+        @NonNull
+        public Builder setSatelliteEntitlementStatus(int isSatelliteEntitlementStatus) {
+            mIsSatelliteEntitlementStatus = isSatelliteEntitlementStatus;
+            return this;
+        }
+
+        /**
+         * Set whether satellite entitlement plmns is empty or not by entitlement query result.
+         *
+         * @param satelliteEntitlementPlmns satellite entitlement plmns is empty or not by
+         * entitlement query result.
+         * @return The builder
+         */
+        @NonNull
+        public Builder setSatelliteEntitlementPlmns(@NonNull String satelliteEntitlementPlmns) {
+            mSatelliteEntitlementPlmns = satelliteEntitlementPlmns;
+            return this;
+        }
+
+        /**
          * Build the {@link SubscriptionInfoInternal}.
          *
          * @return The {@link SubscriptionInfoInternal} instance.
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index b462daf..8757c97 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -16,12 +16,17 @@
 
 package com.android.internal.telephony.subscription;
 
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.compat.CompatChanges;
@@ -44,9 +49,11 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.TelephonyServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Telephony.SimInfo;
 import android.service.carrier.CarrierIdentifier;
@@ -89,11 +96,14 @@
 import com.android.internal.telephony.MultiSimSettingController;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.ProxyController;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.data.PhoneSwitcher;
 import com.android.internal.telephony.euicc.EuiccController;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.subscription.SubscriptionDatabaseManager.SubscriptionDatabaseManagerCallback;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccUtils;
@@ -121,6 +131,7 @@
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 /**
  * The subscription manager service is the backend service of {@link SubscriptionManager}.
@@ -128,6 +139,8 @@
  */
 public class SubscriptionManagerService extends ISub.Stub {
     private static final String LOG_TAG = "SMSVC";
+    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+    private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem";
 
     /** Whether enabling verbose debugging message or not. */
     private static final boolean VDBG = false;
@@ -172,7 +185,11 @@
             SimInfo.COLUMN_VOIMS_OPT_IN_STATUS,
             SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS,
             SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
-            SimInfo.COLUMN_SATELLITE_ENABLED
+            SimInfo.COLUMN_SATELLITE_ENABLED,
+            SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
+            SimInfo.COLUMN_IS_NTN,
+            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS
     );
 
     /**
@@ -184,6 +201,18 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public static final long REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID = 213902861L;
 
+    /**
+     * Apps targeting on Android V and beyond can only see subscriptions accessible by them
+     * according to its user Id.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long FILTER_ACCESSIBLE_SUBS_BY_USER = 296076674L;
+
+    /** Wrap Binder methods for testing. */
+    @NonNull
+    private static final BinderWrapper BINDER_WRAPPER = new BinderWrapper();
+
     /** Instance of subscription manager service. */
     @NonNull
     private static SubscriptionManagerService sInstance;
@@ -192,6 +221,10 @@
     @NonNull
     private final Context mContext;
 
+    /** Feature flags */
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
     /** App Ops manager instance. */
     @NonNull
     private final AppOpsManager mAppOpsManager;
@@ -222,6 +255,10 @@
     @Nullable
     private EuiccController mEuiccController;
 
+    /** Package manager instance. */
+    @NonNull
+    private final PackageManager mPackageManager;
+
     /**
      * The main handler of subscription manager service. This is running on phone process's main
      * thread.
@@ -276,6 +313,12 @@
     private final int[] mSimState;
 
     /**
+     * {@code true} if a user profile can only see the SIMs associated with it, unless it possesses
+     * no SIMs on the device.
+     */
+    private Map<Integer, List<Integer>> mUserIdToAvailableSubs = new ConcurrentHashMap<>();
+
+    /**
      * Slot index/subscription map that automatically invalidate cache in
      * {@link SubscriptionManager}.
      *
@@ -348,6 +391,14 @@
         }
     }
 
+    /** Binder Wrapper for test mocking. */
+    @VisibleForTesting
+    public static class BinderWrapper {
+        @NonNull public UserHandle getCallingUserHandle() {
+            return Binder.getCallingUserHandle();
+        }
+    }
+
     /**
      * This is the callback used for listening events from {@link SubscriptionManagerService}.
      */
@@ -404,14 +455,17 @@
      * @param context The context
      * @param looper The looper for the handler.
      */
-    public SubscriptionManagerService(@NonNull Context context, @NonNull Looper looper) {
+    public SubscriptionManagerService(@NonNull Context context, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags) {
         logl("Created SubscriptionManagerService");
         sInstance = this;
         mContext = context;
+        mFeatureFlags = featureFlags;
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
         mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
         mEuiccManager = context.getSystemService(EuiccManager.class);
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
+        mPackageManager = context.getPackageManager();
 
         mUiccController = UiccController.getInstance();
         mHandler = new Handler(looper);
@@ -479,14 +533,15 @@
         HandlerThread handlerThread = new HandlerThread(LOG_TAG);
         handlerThread.start();
         mSubscriptionDatabaseManager = new SubscriptionDatabaseManager(context,
-                handlerThread.getLooper(), new SubscriptionDatabaseManagerCallback(mHandler::post) {
+                handlerThread.getLooper(), mFeatureFlags,
+                new SubscriptionDatabaseManagerCallback(mHandler::post) {
                     /**
                      * Called when database has been loaded into the cache.
                      */
                     @Override
                     public void onInitialized() {
                         log("Subscription database has been initialized.");
-                        for (int phoneId = 0; phoneId < mTelephonyManager.getActiveModemCount()
+                        for (int phoneId = 0; phoneId < mTelephonyManager.getSupportedModemCount()
                                 ; phoneId++) {
                             markSubscriptionsInactive(phoneId);
                         }
@@ -499,6 +554,8 @@
                      */
                     @Override
                     public void onSubscriptionChanged(int subId) {
+                        updateUserIdToAvailableSubs();
+
                         mSubscriptionManagerServiceCallbacks.forEach(
                                 callback -> callback.invokeFromExecutor(
                                         () -> callback.onSubscriptionChanged(subId)));
@@ -817,6 +874,26 @@
     }
 
     /**
+     * Set whether the subscription ID supports oem satellite or not.
+     *
+     * @param subId The subscription ID.
+     * @param isNtn {@code true} Requested subscription ID supports oem satellite service,
+     * {@code false} otherwise.
+     */
+    public void setNtn(int subId, boolean isNtn) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            return;
+        }
+
+        // This can throw IllegalArgumentException if the subscription does not exist.
+        try {
+            mSubscriptionDatabaseManager.setNtn(subId, (isNtn ? 1 : 0));
+        } catch (IllegalArgumentException e) {
+            loge("setOnlyNonTerrestrialNetwork: invalid subId=" + subId);
+        }
+    }
+
+    /**
      * Set ISO country code by subscription id.
      *
      * @param iso ISO country code associated with the subscription.
@@ -848,6 +925,26 @@
     }
 
     /**
+     * Set the group owner on the subscription
+     *
+     * <p> Note: This only sets the group owner field and doesn't update other relevant fields.
+     * Prefer to call {@link #addSubscriptionsIntoGroup}.
+     *
+     * @param subId Subscription id.
+     * @param groupOwner The group owner to assign to the subscription
+     */
+    public void setGroupOwner(int subId, @NonNull String groupOwner) {
+        // This can throw IllegalArgumentException if the subscription does not exist.
+        try {
+            mSubscriptionDatabaseManager.setGroupOwner(
+                    subId,
+                    groupOwner);
+        } catch (IllegalArgumentException e) {
+            loge("setManaged: invalid subId=" + subId);
+        }
+    }
+
+    /**
      * Set last used TP message reference.
      *
      * @param subId Subscription id.
@@ -1095,7 +1192,27 @@
                         builder.setDisplayName(nickName);
                         builder.setDisplayNameSource(SubscriptionManager.NAME_SOURCE_CARRIER);
                     }
-                    builder.setProfileClass(embeddedProfile.getProfileClass());
+
+                    boolean isSatelliteSpn = false;
+                    if (mFeatureFlags.oemEnabledSatelliteFlag() ) {
+                        if (isSatelliteSpn(embeddedProfile.getServiceProviderName())) {
+                            isSatelliteSpn = true;
+                            builder.setOnlyNonTerrestrialNetwork(1);
+                        }
+                    } else {
+                        log("updateEmupdateEmbeddedSubscriptions: oemEnabledSatelliteFlag is "
+                                + "disabled");
+                    }
+
+                    if (android.os.Build.isDebuggable() &&
+                            SystemProperties.getInt("telephony.test.bootstrap_cid", -2)
+                                == carrierId) {
+                        // Force set as provisioning profile for test purpose
+                        log("Hardcording as bootstrap subscription for cid=" + carrierId);
+                        builder.setProfileClass(SimInfo.PROFILE_CLASS_PROVISIONING);
+                    } else {
+                        builder.setProfileClass(embeddedProfile.getProfileClass());
+                    }
                     builder.setPortIndex(getPortIndex(embeddedProfile.getIccid()));
 
                     CarrierIdentifier cid = embeddedProfile.getCarrierIdentifier();
@@ -1111,6 +1228,10 @@
                         String mnc = cid.getMnc();
                         builder.setMcc(mcc);
                         builder.setMnc(mnc);
+                        if (mFeatureFlags.oemEnabledSatelliteFlag() && !isSatelliteSpn) {
+                            builder.setOnlyNonTerrestrialNetwork(
+                                    isSatellitePlmn(mcc + mnc) ? 1 : 0);
+                        }
                     }
                     // If cardId = unsupported or un-initialized, we have no reason to update DB.
                     // Additionally, if the device does not support cardId for default eUICC, the
@@ -1121,7 +1242,11 @@
                         builder.setCardString(mUiccController.convertToCardString(cardId));
                     }
 
+                    if (mFeatureFlags.supportPsimToEsimConversion()) {
+                        builder.setTransferStatus(subInfo.getTransferStatus());
+                    }
                     embeddedSubs.add(subInfo.getSubscriptionId());
+
                     subInfo = builder.build();
                     log("updateEmbeddedSubscriptions: update subscription " + subInfo);
                     mSubscriptionDatabaseManager.updateSubscription(subInfo);
@@ -1424,6 +1549,12 @@
                         loge("updateSubscription: ICC card is not available.");
                     }
 
+                    if (Flags.clearCachedImsPhoneNumberWhenDeviceLostImsRegistration()) {
+                        // Clear the cached Ims phone number
+                        // before proceeding with Ims Registration
+                        setNumberFromIms(subId, new String(""));
+                    }
+
                     // Attempt to restore SIM specific settings when SIM is loaded.
                     Bundle result = mContext.getContentResolver().call(
                             SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
@@ -1612,10 +1743,44 @@
                             preferredUsageSetting)
                     + " newSetting=" + SubscriptionManager.usageSettingToString(newUsageSetting));
         }
+
+        if (mFeatureFlags.dataOnlyCellularService()) {
+            final int[] servicesFromCarrierConfig =
+                    config.getIntArray(
+                            CarrierConfigManager.KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY);
+            int serviceBitmasks = 0;
+            boolean allServicesAreValid = true;
+            // Check if all services from carrier config are valid before setting to db
+            if (servicesFromCarrierConfig == null) {
+                allServicesAreValid = false;
+            } else {
+                for (int service : servicesFromCarrierConfig) {
+                    if (service < SubscriptionManager.SERVICE_CAPABILITY_VOICE
+                            || service > SubscriptionManager.SERVICE_CAPABILITY_MAX) {
+                        allServicesAreValid = false;
+                        break;
+                    } else {
+                        serviceBitmasks |= SubscriptionManager.serviceCapabilityToBitmask(service);
+                    }
+                }
+            }
+            // In case we get invalid service override, fall back to default value.
+            // DO NOT throw exception which will crash phone process.
+            if (!allServicesAreValid) {
+                serviceBitmasks = SubscriptionManager.getAllServiceCapabilityBitmasks();
+            }
+
+            if (serviceBitmasks != subInfo.getServiceCapabilities()) {
+                log("updateSubscriptionByCarrierConfig: serviceCapabilities updated from "
+                        + subInfo.getServiceCapabilities() + " to " + serviceBitmasks);
+                mSubscriptionDatabaseManager.setServiceCapabilities(subId, serviceBitmasks);
+            }
+        }
     }
 
     /**
-     * Get all subscription info records from SIMs that are inserted now or previously inserted.
+     * Get all subscription info records from SIMs visible to the calling user that are inserted now
+     * or previously inserted.
      *
      * <p>
      * If the caller does not have {@link Manifest.permission#READ_PHONE_NUMBERS} permission,
@@ -1656,7 +1821,9 @@
                     + "carrier privilege");
         }
 
-        return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
+        enforceTelephonyFeatureWithException(callingPackage, "getAllSubInfoList");
+
+        return getSubscriptionInfoStreamAsUser(BINDER_WRAPPER.getCallingUserHandle())
                 // callers have READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE can get a full
                 // list. Carrier apps can only get the subscriptions they have privileged.
                 .filter(subInfo -> TelephonyPermissions.checkCallingOrSelfReadPhoneStateNoThrow(
@@ -1698,6 +1865,8 @@
                     + "carrier privilege");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfo");
+
         SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
                 .getSubscriptionInfoInternal(subId);
         if (subInfo != null && subInfo.isActive()) {
@@ -1726,6 +1895,8 @@
         enforcePermissions("getActiveSubscriptionInfoForIccId",
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfoForIccId");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             iccId = IccUtils.stripTrailingFs(iccId);
@@ -1770,6 +1941,9 @@
 
         }
 
+        enforceTelephonyFeatureWithException(callingPackage,
+                "getActiveSubscriptionInfoForSimSlotIndex");
+
         if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
             throw new IllegalArgumentException("Invalid slot index " + slotIndex);
         }
@@ -1785,12 +1959,14 @@
     }
 
     /**
-     * Get the SubscriptionInfo(s) of the active subscriptions. The records will be sorted
-     * by {@link SubscriptionInfo#getSimSlotIndex} then by
+     * Get the SubscriptionInfo(s) of the active subscriptions for calling user. The records will be
+     * sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
      * {@link SubscriptionInfo#getSubscriptionId}.
      *
      * @param callingPackage The package making the call.
      * @param callingFeatureId The feature in the package.
+     * @param isForAllProfiles whether the caller intends to see all subscriptions regardless
+     *                      association.
      *
      * @return Sorted list of the currently {@link SubscriptionInfo} records available on the
      * device.
@@ -1803,13 +1979,13 @@
             "carrier privileges",
     })
     public List<SubscriptionInfo> getActiveSubscriptionInfoList(@NonNull String callingPackage,
-            @Nullable String callingFeatureId) {
+            @Nullable String callingFeatureId, boolean isForAllProfiles) {
         // Check if the caller has READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or carrier
         // privilege on any active subscription. The carrier app will get full subscription infos
         // on the subs it has carrier privilege.
         if (!TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(mContext,
                 Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, callingFeatureId,
-                "getAllSubInfoList")) {
+                "getActiveSubscriptionInfoList")) {
             // Ideally we should avoid silent failure, but since this API has already been used by
             // many apps and they do not expect the security exception, we return an empty list
             // here so it's consistent with pre-U behavior.
@@ -1818,13 +1994,19 @@
             return Collections.emptyList();
         }
 
-        return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
+        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfoList");
+
+        if (isForAllProfiles) {
+            enforcePermissionAccessAllUserProfiles();
+        }
+        return getSubscriptionInfoStreamAsUser(isForAllProfiles
+                ? UserHandle.ALL : BINDER_WRAPPER.getCallingUserHandle())
                 .filter(SubscriptionInfoInternal::isActive)
                 // Remove the identifier if the caller does not have sufficient permission.
                 // carrier apps will get full subscription info on the subscriptions associated
                 // to them.
                 .map(subInfo -> conditionallyRemoveIdentifiers(subInfo.toSubscriptionInfo(),
-                        callingPackage, callingFeatureId, "getAllSubInfoList"))
+                        callingPackage, callingFeatureId, "getActiveSubscriptionInfoList"))
                 .sorted(Comparator.comparing(SubscriptionInfo::getSimSlotIndex)
                         .thenComparing(SubscriptionInfo::getSubscriptionId))
                 .collect(Collectors.toList());
@@ -1835,6 +2017,8 @@
      *
      * @param callingPackage The package making the call.
      * @param callingFeatureId The feature in the package.
+     * @param isForAllProfiles whether the caller intends to see all subscriptions regardless
+     *                        association.
      *
      * @return the number of active subscriptions.
      *
@@ -1847,20 +2031,30 @@
             "carrier privileges",
     })
     public int getActiveSubInfoCount(@NonNull String callingPackage,
-            @Nullable String callingFeatureId) {
+            @Nullable String callingFeatureId, boolean isForAllProfiles) {
         if (!TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(mContext,
                 Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, callingFeatureId,
                 "getAllSubInfoList")) {
             throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
                     + "carrier privilege");
         }
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            return getActiveSubIdList(false).length;
-        } finally {
-            Binder.restoreCallingIdentity(token);
+        if (isForAllProfiles) {
+            enforcePermissionAccessAllUserProfiles();
         }
+
+        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubInfoCount");
+
+        return getActiveSubIdListAsUser(false, isForAllProfiles
+                ? UserHandle.ALL : BINDER_WRAPPER.getCallingUserHandle()).length;
+    }
+
+    /** @throws SecurityException if caller doesn't have one of the requested permissions. */
+    private void enforcePermissionAccessAllUserProfiles() {
+        if (!mFeatureFlags.enforceSubscriptionUserFilter()) return;
+        enforcePermissions("To access across profiles",
+                Manifest.permission.INTERACT_ACROSS_USERS,
+                Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                Manifest.permission.INTERACT_ACROSS_PROFILES);
     }
 
     /**
@@ -1892,26 +2086,44 @@
         enforcePermissions("getAvailableSubscriptionInfoList",
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
-        // Now that all security checks pass, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            // Available eSIM profiles are reported by EuiccManager. However for physical SIMs if
-            // they are in inactive slot or programmatically disabled, they are still considered
-            // available. In this case we get their iccid from slot info and include their
-            // subscriptionInfos.
-            List<String> iccIds = getIccIdsOfInsertedPhysicalSims();
+        enforceTelephonyFeatureWithException(callingPackage, "getAvailableSubscriptionInfoList");
 
-            return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
-                    .filter(subInfo -> subInfo.isActive() || iccIds.contains(subInfo.getIccId())
-                            || (mEuiccManager != null && mEuiccManager.isEnabled()
-                            && subInfo.isEmbedded()))
-                    .map(SubscriptionInfoInternal::toSubscriptionInfo)
-                    .sorted(Comparator.comparing(SubscriptionInfo::getSimSlotIndex)
-                            .thenComparing(SubscriptionInfo::getSubscriptionId))
-                    .collect(Collectors.toList());
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        return getAvailableSubscriptionsInternalStream()
+                .sorted(Comparator.comparing(SubscriptionInfoInternal::getSimSlotIndex)
+                        .thenComparing(SubscriptionInfoInternal::getSubscriptionId))
+                .map(SubscriptionInfoInternal::toSubscriptionInfo)
+                .collect(Collectors.toList());
+
+    }
+
+    /**
+     * @return all the subscriptions visible to user on the device.
+     */
+    private Stream<SubscriptionInfoInternal> getAvailableSubscriptionsInternalStream() {
+        // Available eSIM profiles are reported by EuiccManager. However for physical SIMs if
+        // they are in inactive slot or programmatically disabled, they are still considered
+        // available. In this case we get their iccid from slot info and include their
+        // subscriptionInfos.
+        List<String> iccIds = getIccIdsOfInsertedPhysicalSims();
+
+        return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
+                .filter(subInfo -> subInfo.isActive() || iccIds.contains(subInfo.getIccId())
+                        || (mEuiccManager != null && mEuiccManager.isEnabled()
+                        && subInfo.isEmbedded()));
+    }
+
+    /**
+     * Tracks for each user Id, a list of subscriptions associated with it.
+     * A profile is barred from seeing unassociated subscriptions if it has its own subscription
+     * which is available to choose from the device.
+     */
+    private void updateUserIdToAvailableSubs() {
+        mUserIdToAvailableSubs = getAvailableSubscriptionsInternalStream()
+                .collect(Collectors.groupingBy(
+                        SubscriptionInfoInternal::getUserId,
+                        Collectors.mapping(SubscriptionInfoInternal::getSubscriptionId,
+                                Collectors.toList())));
+        log("updateUserIdToAvailableSubs: " + mUserIdToAvailableSubs);
     }
 
     /**
@@ -1946,8 +2158,7 @@
 
         // Verify that the callingPackage belongs to the calling UID
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
-
-        return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
+        return getSubscriptionInfoStreamAsUser(BINDER_WRAPPER.getCallingUserHandle())
                 .map(SubscriptionInfoInternal::toSubscriptionInfo)
                 .filter(subInfo -> subInfo.isEmbedded()
                         && mSubscriptionManager.canManageSubscription(subInfo, callingPackage))
@@ -1961,6 +2172,8 @@
      */
     @Override
     public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
+        enforcePermissions("requestEmbeddedSubscriptionInfoListRefresh",
+                Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS);
         updateEmbeddedSubscriptions(List.of(cardId), null);
     }
 
@@ -1975,6 +2188,7 @@
      * @return 0 if success, < 0 on error
      *
      * @throws SecurityException if the caller does not have required permissions.
+     * @throws IllegalArgumentException if {@code slotIndex} is invalid.
      */
     @Override
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -1986,6 +2200,13 @@
                 + SubscriptionManager.subscriptionTypeToString(subscriptionType) + ", "
                 + getCallingPackage());
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "addSubInfo");
+
+        if (!SubscriptionManager.isValidSlotIndex(slotIndex)
+                && slotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            throw new IllegalArgumentException("Invalid slotIndex " + slotIndex);
+        }
+
         // Now that all security checks passes, perform the operation as ourselves.
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -2041,6 +2262,9 @@
         logl("removeSubInfo: uniqueId=" + SubscriptionInfo.getPrintableId(uniqueId) + ", "
                 + SubscriptionManager.subscriptionTypeToString(subscriptionType) + ", "
                 + getCallingPackage());
+
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "removeSubInfo");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
@@ -2264,6 +2488,8 @@
                 mContext, Binder.getCallingUid(), subId, true, "setOpportunistic",
                 Manifest.permission.MODIFY_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(callingPackage, "setOpportunistic");
+
         long token = Binder.clearCallingIdentity();
         try {
             mSubscriptionDatabaseManager.setOpportunistic(subId, opportunistic);
@@ -2316,6 +2542,8 @@
                     + " carrier privilege permission on all specified subscriptions");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "createSubscriptionGroup");
+
         long identity = Binder.clearCallingIdentity();
 
         try {
@@ -2352,6 +2580,10 @@
             @Nullable ISetOpportunisticDataCallback callback) {
         enforcePermissions("setPreferredDataSubscriptionId",
                 Manifest.permission.MODIFY_PHONE_STATE);
+
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "setPreferredDataSubscriptionId");
+
         final long token = Binder.clearCallingIdentity();
 
         try {
@@ -2438,6 +2670,8 @@
             return Collections.emptyList();
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getOpportunisticSubscriptions");
+
         return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
                 // callers have READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE can get a full
                 // list. Carrier apps can only get the subscriptions they have privileged.
@@ -2490,6 +2724,8 @@
             throw new IllegalArgumentException("subIdList is empty.");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "removeSubscriptionsFromGroup");
+
         long identity = Binder.clearCallingIdentity();
 
         try {
@@ -2572,6 +2808,8 @@
                     + " permissions on subscriptions and the group.");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "addSubscriptionsIntoGroup");
+
         long identity = Binder.clearCallingIdentity();
 
         try {
@@ -2641,6 +2879,8 @@
             }
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getSubscriptionsInGroup");
+
         return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
                 .map(SubscriptionInfoInternal::toSubscriptionInfo)
                 .filter(info -> groupUuid.equals(info.getGroupUuid())
@@ -2738,10 +2978,43 @@
 
     /**
      * @return The default subscription id.
+     * @deprecated Use {@link #getDefaultSubIdAsUser}.
      */
     @Override
     public int getDefaultSubId() {
-        return mDefaultSubId.get();
+        return getDefaultSubIdAsUser(BINDER_WRAPPER.getCallingUserHandle().getIdentifier());
+    }
+
+    /**
+     * @param userId The given user Id to check.
+     * @return The default subscription id.
+     */
+    @Override
+    public int getDefaultSubIdAsUser(@UserIdInt int userId) {
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "getDefaultVoiceSubIdAsUser");
+
+        return getDefaultAsUser(userId, mDefaultSubId.get());
+    }
+
+    /**
+     * Get the default subscription visible to the caller.
+     * @param userId The calling user Id.
+     * @param defaultValue Useful if the user owns more than one subscription.
+     * @return The subscription Id default to use.
+     */
+    private int getDefaultAsUser(@UserIdInt int userId, int defaultValue) {
+        // TODO: Not using mFlags.enforceSubscriptionUserFilter because this affects U CTS.
+        if (mFeatureFlags.workProfileApiSplit()) {
+            List<SubscriptionInfoInternal> subInfos =
+                    getSubscriptionInfoStreamAsUser(UserHandle.of(userId))
+                            .filter(SubscriptionInfoInternal::isActive)
+                            .toList();
+            if (subInfos.size() == 1) {
+                return subInfos.get(0).getSubscriptionId();
+            }
+        }
+        return defaultValue;
     }
 
     /**
@@ -2797,9 +3070,13 @@
             throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUBSCRIPTION_ID");
         }
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "setDefaultDataSubId");
+
         final long token = Binder.clearCallingIdentity();
         try {
             if (mDefaultDataSubId.set(subId)) {
+                remapRafIfApplicable();
+
                 MultiSimSettingController.getInstance().notifyDefaultDataSubChanged();
 
                 broadcastSubId(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED,
@@ -2813,11 +3090,38 @@
     }
 
     /**
+     * Remap Radio Access Family if needed.
+     */
+    private void remapRafIfApplicable() {
+        boolean applicable = mSlotIndexToSubId.containsValue(getDefaultDataSubId());
+        if (!applicable) return;
+        ProxyController proxyController = ProxyController.getInstance();
+        RadioAccessFamily[] rafs = new RadioAccessFamily[mTelephonyManager.getActiveModemCount()];
+        for (int phoneId = 0; phoneId < rafs.length; phoneId++) {
+            int raf = mSlotIndexToSubId.getOrDefault(phoneId,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID) == getDefaultDataSubId()
+                    ? proxyController.getMaxRafSupported() : proxyController.getMinRafSupported();
+            rafs[phoneId] = new RadioAccessFamily(phoneId, raf);
+        }
+        proxyController.setRadioCapability(rafs);
+    }
+
+    /**
      * @return The default subscription id for voice.
+     * @deprecated Use {@link #getDefaultVoiceSubIdAsUser}.
      */
     @Override
     public int getDefaultVoiceSubId() {
-        return mDefaultVoiceSubId.get();
+        return getDefaultVoiceSubIdAsUser(BINDER_WRAPPER.getCallingUserHandle().getIdentifier());
+    }
+
+    /**
+     * @param userId The calling user Id.
+     * @return The default voice subscription id.
+     */
+    @Override
+    public int getDefaultVoiceSubIdAsUser(@UserIdInt int userId) {
+        return getDefaultAsUser(userId, mDefaultVoiceSubId.get());
     }
 
     /**
@@ -2836,6 +3140,8 @@
             throw new RuntimeException("setDefaultVoiceSubId called with DEFAULT_SUB_ID");
         }
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "setDefaultVoiceSubId");
+
         final long token = Binder.clearCallingIdentity();
         try {
             if (mDefaultVoiceSubId.set(subId)) {
@@ -2860,10 +3166,24 @@
 
     /**
      * @return The default subscription id for SMS.
+     * @deprecated Use {@link #getDefaultSmsSubIdAsUser}.
      */
     @Override
     public int getDefaultSmsSubId() {
-        return mDefaultSmsSubId.get();
+        return getDefaultSmsSubIdAsUser(BINDER_WRAPPER.getCallingUserHandle().getIdentifier());
+    }
+
+    /**
+     * Get the default sms subscription id associated with the user. When a subscription is
+     * associated with personal profile or work profile, the default sms subscription id will be
+     * always the subscription it is associated with.
+     *
+     * @param userId The given user Id to check.
+     * @return The default voice id.
+     */
+    @Override
+    public int getDefaultSmsSubIdAsUser(@UserIdInt int userId) {
+        return getDefaultAsUser(userId, mDefaultSmsSubId.get());
     }
 
     /**
@@ -2882,6 +3202,8 @@
             throw new RuntimeException("setDefaultSmsSubId called with DEFAULT_SUB_ID");
         }
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "setDefaultSmsSubId");
+
         final long token = Binder.clearCallingIdentity();
         try {
             if (mDefaultSmsSubId.set(subId)) {
@@ -2921,18 +3243,32 @@
     public int[] getActiveSubIdList(boolean visibleOnly) {
         enforcePermissions("getActiveSubIdList", Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
-        final long token = Binder.clearCallingIdentity();
-        try {
-            return mSlotIndexToSubId.values().stream()
-                    .filter(subId -> {
-                        SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
-                                .getSubscriptionInfoInternal(subId);
-                        return subInfo != null && (!visibleOnly || subInfo.isVisible()); })
-                    .mapToInt(x -> x)
-                    .toArray();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "getActiveSubIdList");
+
+        // UserHandle.ALL because this API is exposed as system API.
+        return getActiveSubIdListAsUser(visibleOnly, UserHandle.ALL);
+    }
+
+    /**
+     * Get the active subscription id list as user.
+     * Must be used before clear Binder identity.
+     *
+     * @param visibleOnly {@code true} if only includes user visible subscription's sub id.
+     * @param user If {@code null}, uses the calling user handle to judge which subscriptions are
+     *             accessible to the caller.
+     * @return List of the active subscription id.
+     */
+    private int[] getActiveSubIdListAsUser(boolean visibleOnly, @NonNull final UserHandle user) {
+        return mSlotIndexToSubId.values().stream()
+                .filter(subId -> {
+                    SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
+                            .getSubscriptionInfoInternal(subId);
+                    return subInfo != null && (!visibleOnly || subInfo.isVisible())
+                            && isSubscriptionAssociatedWithUserInternal(
+                                    subInfo, user.getIdentifier());
+                })
+                .mapToInt(x -> x)
+                .toArray();
     }
 
     /**
@@ -3020,6 +3356,8 @@
                     + "accessed through getSubscriptionProperty.");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getSubscriptionProperty");
+
         final long token = Binder.clearCallingIdentity();
         try {
             Object value = mSubscriptionDatabaseManager.getSubscriptionProperty(subId, columnName);
@@ -3064,6 +3402,8 @@
             throw new IllegalArgumentException("Invalid subscription id " + subId);
         }
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "isSubscriptionEnabled");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
@@ -3093,6 +3433,8 @@
             throw new IllegalArgumentException("Invalid slot index " + slotIndex);
         }
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "getEnabledSubscriptionId");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
@@ -3129,6 +3471,9 @@
             throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
                     + "carrier privilege");
         }
+
+        enforceTelephonyFeatureWithException(callingPackage, "isActiveSubId");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
@@ -3186,6 +3531,9 @@
         enforcePermissions("canDisablePhysicalSubscription",
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "canDisablePhysicalSubscription");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             Phone phone = PhoneFactory.getDefaultPhone();
@@ -3219,6 +3567,9 @@
         logl("setUiccApplicationsEnabled: subId=" + subId + ", enabled=" + enabled
                 + ", calling package=" + getCallingPackage());
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "setUiccApplicationsEnabled");
+
         final long identity = Binder.clearCallingIdentity();
         try {
 
@@ -3261,6 +3612,9 @@
         enforcePermissions("setDeviceToDeviceStatusSharing",
                 Manifest.permission.MODIFY_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "setDeviceToDeviceStatusSharing");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             if (sharing < SubscriptionManager.D2D_SHARING_DISABLED
@@ -3293,6 +3647,9 @@
         enforcePermissions("setDeviceToDeviceStatusSharingContacts",
                 Manifest.permission.MODIFY_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "setDeviceToDeviceStatusSharingContacts");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             Objects.requireNonNull(contacts, "contacts");
@@ -3360,6 +3717,8 @@
                 Manifest.permission.READ_PHONE_NUMBERS,
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(callingPackage, "getPhoneNumber");
+
         final long identity = Binder.clearCallingIdentity();
 
         SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
@@ -3420,6 +3779,9 @@
                 Manifest.permission.READ_PHONE_NUMBERS,
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(callingPackage,
+                "getPhoneNumberFromFirstAvailableSource");
+
         String numberFromCarrier = getPhoneNumber(subId,
                 SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, callingPackage,
                 callingFeatureId);
@@ -3467,6 +3829,8 @@
                     + SubscriptionManager.phoneNumberSourceToString(source));
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "setPhoneNumber");
+
         Objects.requireNonNull(number, "number");
 
         final long identity = Binder.clearCallingIdentity();
@@ -3570,7 +3934,6 @@
             }
 
             UserHandle userHandle = UserHandle.of(subInfo.getUserId());
-            logv("getSubscriptionUserHandle subId = " + subId + " userHandle = " + userHandle);
             if (userHandle.getIdentifier() == UserHandle.USER_NULL) {
                 return null;
             }
@@ -3581,16 +3944,35 @@
     }
 
     /**
+     * Returns whether the given subscription is associated with the calling user.
+     *
+     * @param subscriptionId the subscription ID of the subscription
+     * @return {@code true} if the subscription is associated with the user that the calling process
+     *         is running in; {@code false} otherwise.
+     *
+     * @throws IllegalArgumentException if subscription doesn't exist.
+     * @throws SecurityException if the caller doesn't have permissions required.
+     */
+    @Override
+    public boolean isSubscriptionAssociatedWithCallingUser(int subscriptionId) {
+        enforcePermissions("isSubscriptionAssociatedWithCallingUser",
+                Manifest.permission.READ_PHONE_STATE);
+
+        UserHandle myUserHandle = UserHandle.of(UserHandle.getCallingUserId());
+        return mFeatureFlags.subscriptionUserAssociationQuery()
+            && isSubscriptionAssociatedWithUserNoCheck(subscriptionId, myUserHandle);
+    }
+
+    /**
      * Check if subscription and user are associated with each other.
      *
      * @param subscriptionId the subId of the subscription
      * @param userHandle user handle of the user
      * @return {@code true} if subscription is associated with user
-     * {@code true} if there are no subscriptions on device
      * else {@code false} if subscription is not associated with user.
      *
      * @throws SecurityException if the caller doesn't have permissions required.
-     *
+     * @throws IllegalArgumentException if the subscription has no records on device.
      */
     @Override
     public boolean isSubscriptionAssociatedWithUser(int subscriptionId,
@@ -3598,32 +3980,33 @@
         enforcePermissions("isSubscriptionAssociatedWithUser",
                 Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
 
+        return isSubscriptionAssociatedWithUserNoCheck(subscriptionId, userHandle);
+    }
+
+    private boolean isSubscriptionAssociatedWithUserNoCheck(int subscriptionId,
+            @NonNull UserHandle userHandle) {
+        SubscriptionInfoInternal subInfoInternal = mSubscriptionDatabaseManager
+                .getSubscriptionInfoInternal(subscriptionId);
+        // Throw IAE if no record of the sub's association state.
+        if (subInfoInternal == null) {
+            throw new IllegalArgumentException(
+                    "[isSubscriptionAssociatedWithUser]: Subscription doesn't exist: "
+                            + subscriptionId);
+        }
+
+        if (mFeatureFlags.enforceSubscriptionUserFilter()) {
+            return isSubscriptionAssociatedWithUserInternal(
+                    subInfoInternal, userHandle.getIdentifier());
+        }
+
         long token = Binder.clearCallingIdentity();
         try {
-            // Return true if there are no subscriptions on the device.
-            List<SubscriptionInfo> subInfoList = getAllSubInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (subInfoList == null || subInfoList.isEmpty()) {
-                return true;
-            }
-
-            List<Integer> subIdList = subInfoList.stream().map(SubscriptionInfo::getSubscriptionId)
-                    .collect(Collectors.toList());
-            if (!subIdList.contains(subscriptionId)) {
-                // Return true as this subscription is not available on the device.
-                return true;
-            }
-
             // Get list of subscriptions associated with this user.
             List<SubscriptionInfo> associatedSubscriptionsList =
                     getSubscriptionInfoListAssociatedWithUser(userHandle);
-            if (associatedSubscriptionsList.isEmpty()) {
-                return false;
-            }
-
             // Return true if required subscription is present in associated subscriptions list.
             for (SubscriptionInfo subInfo: associatedSubscriptionsList) {
-                if (subInfo.getSubscriptionId() == subscriptionId){
+                if (subInfo.getSubscriptionId() == subscriptionId) {
                     return true;
                 }
             }
@@ -3634,6 +4017,25 @@
     }
 
     /**
+     * @param subInfo The subscription info to check.
+     * @param userId The caller user Id.
+     * @return {@code true} if the given user Id is allowed to access to the given subscription.
+     */
+    private boolean isSubscriptionAssociatedWithUserInternal(
+            @NonNull SubscriptionInfoInternal subInfo, @UserIdInt int userId) {
+        if (!mFeatureFlags.enforceSubscriptionUserFilter()
+                || !CompatChanges.isChangeEnabled(FILTER_ACCESSIBLE_SUBS_BY_USER,
+                Binder.getCallingUid())) {
+            return true;
+        }
+        // Can access the unassociated sub if the user doesn't have its own.
+        return (subInfo.getUserId() == UserHandle.USER_NULL
+                && mUserIdToAvailableSubs.get(userId) == null)
+                || userId == subInfo.getUserId()
+                || userId == UserHandle.USER_ALL;
+    }
+
+    /**
      * Get list of subscriptions associated with user.
      *
      * If user handle is associated with some subscriptions, return subscriptionsAssociatedWithUser
@@ -3646,50 +4048,70 @@
      *
      */
     @Override
-    public @NonNull List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(
+    @NonNull
+    public List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(
             @NonNull UserHandle userHandle) {
         enforcePermissions("getSubscriptionInfoListAssociatedWithUser",
                 Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
 
+        if (mFeatureFlags.enforceSubscriptionUserFilter()) {
+            return getSubscriptionInfoStreamAsUser(userHandle)
+                    .map(SubscriptionInfoInternal::toSubscriptionInfo)
+                    .collect(Collectors.toList());
+        }
+
         long token = Binder.clearCallingIdentity();
         try {
-            List<SubscriptionInfo> subInfoList =  getAllSubInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (subInfoList == null || subInfoList.isEmpty()) {
+            List<SubscriptionInfoInternal> subInfoList =  mSubscriptionDatabaseManager
+                    .getAllSubscriptions();
+            if (subInfoList.isEmpty()) {
                 return new ArrayList<>();
             }
 
             List<SubscriptionInfo> subscriptionsAssociatedWithUser = new ArrayList<>();
             List<SubscriptionInfo> subscriptionsWithNoAssociation = new ArrayList<>();
-            for (SubscriptionInfo subInfo : subInfoList) {
-                int subId = subInfo.getSubscriptionId();
-                UserHandle subIdUserHandle = getSubscriptionUserHandle(subId);
-                if (userHandle.equals(subIdUserHandle)) {
+            for (SubscriptionInfoInternal subInfo : subInfoList) {
+                if (subInfo.getUserId() == userHandle.getIdentifier()) {
                     // Store subscriptions whose user handle matches with required user handle.
-                    subscriptionsAssociatedWithUser.add(subInfo);
-                } else if (subIdUserHandle == null) {
+                    subscriptionsAssociatedWithUser.add(subInfo.toSubscriptionInfo());
+                } else if (subInfo.getUserId() == UserHandle.USER_NULL) {
                     // Store subscriptions whose user handle is set to null.
-                    subscriptionsWithNoAssociation.add(subInfo);
+                    subscriptionsWithNoAssociation.add(subInfo.toSubscriptionInfo());
                 }
             }
 
             UserManager userManager = mContext.getSystemService(UserManager.class);
             if ((userManager != null)
                     && (userManager.isManagedProfile(userHandle.getIdentifier()))) {
-                // For work profile, return subscriptions associated only with work profile
+                // For work profile, return subscriptions associated only with work profile even
+                // if it's empty.
                 return subscriptionsAssociatedWithUser;
             }
 
-            // For all other profiles, if subscriptionsAssociatedWithUser is empty return all the
-            // subscriptionsWithNoAssociation.
-            return subscriptionsAssociatedWithUser.isEmpty() ?
-                    subscriptionsWithNoAssociation : subscriptionsAssociatedWithUser;
+            // For all other profiles, if subscriptionsAssociatedWithUser is empty return all
+            // the subscriptionsWithNoAssociation.
+            return subscriptionsAssociatedWithUser.isEmpty()
+                    ? subscriptionsWithNoAssociation : subscriptionsAssociatedWithUser;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
     /**
+     * Get subscriptions accessible to the caller user.
+     *
+     * @param user The user to check.
+     * @return a stream of accessible internal subscriptions.
+     */
+    @NonNull
+    private Stream<SubscriptionInfoInternal> getSubscriptionInfoStreamAsUser(
+            @NonNull final UserHandle user) {
+        return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
+                .filter(info -> isSubscriptionAssociatedWithUserInternal(
+                        info, user.getIdentifier()));
+    }
+
+    /**
      * Called during setup wizard restore flow to attempt to restore the backed up sim-specific
      * configs to device for all existing SIMs in the subscription database {@link SimInfo}.
      * Internally, it will store the backup data in an internal file. This file will persist on
@@ -3709,6 +4131,9 @@
         enforcePermissions("restoreAllSimSpecificSettingsFromBackup",
                 Manifest.permission.MODIFY_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "restoreAllSimSpecificSettingsFromBackup");
+
         long token = Binder.clearCallingIdentity();
         try {
             Bundle bundle = new Bundle();
@@ -3756,14 +4181,26 @@
      * @throws SecurityException if the caller does not have any permissions.
      */
     private void enforcePermissions(@Nullable String message, @NonNull String ...permissions) {
+        if (!hasPermissions(permissions)) {
+            throw new SecurityException(
+                    message + ". Does not have any of the following permissions. "
+                            + Arrays.toString(permissions));
+        }
+    }
+
+    /**
+     * Check have any of the permissions
+     * @param permissions The permissions to check.
+     * @return {@code true} if the caller has one of the given permissions.
+     */
+    private boolean hasPermissions(@NonNull String ...permissions) {
         for (String permission : permissions) {
             if (mContext.checkCallingOrSelfPermission(permission)
                     == PackageManager.PERMISSION_GRANTED) {
-                return;
+                return true;
             }
         }
-        throw new SecurityException(message + ". Does not have any of the following permissions. "
-                + Arrays.toString(permissions));
+        return false;
     }
 
     /**
@@ -3787,9 +4224,8 @@
      */
     @Nullable
     public SubscriptionInfo getSubscriptionInfo(int subId) {
-        SubscriptionInfoInternal subscriptionInfoInternal = getSubscriptionInfoInternal(subId);
-        return subscriptionInfoInternal != null
-                ? subscriptionInfoInternal.toSubscriptionInfo() : null;
+        SubscriptionInfoInternal infoInternal = getSubscriptionInfoInternal(subId);
+        return infoInternal != null ? infoInternal.toSubscriptionInfo() : null;
     }
 
     /**
@@ -3906,8 +4342,11 @@
      */
     @VisibleForTesting
     public void updateGroupDisabled() {
-        List<SubscriptionInfo> activeSubscriptions = getActiveSubscriptionInfoList(
-                mContext.getOpPackageName(), mContext.getFeatureId());
+        List<SubscriptionInfo> activeSubscriptions = mSubscriptionDatabaseManager
+                .getAllSubscriptions().stream()
+                .filter(SubscriptionInfoInternal::isActive)
+                .map(SubscriptionInfoInternal::toSubscriptionInfo)
+                .collect(Collectors.toList());
         for (SubscriptionInfo oppSubInfo : getOpportunisticSubscriptions(
                 mContext.getOpPackageName(), mContext.getFeatureId())) {
             boolean groupDisabled = activeSubscriptions.stream()
@@ -3918,6 +4357,104 @@
         }
     }
 
+
+
+    /**
+     * Set the transfer status of the subscriptionInfo that corresponds to subId.
+     * @param subId The unique SubscriptionInfo key in database.
+     * @param status The transfer status to change. This value must be one of the following.
+     * {@link SubscriptionManager#TRANSFER_STATUS_NONE},
+     * {@link SubscriptionManager#TRANSFER_STATUS_TRANSFERRED_OUT} or
+     * {@link SubscriptionManager#TRANSFER_STATUS_CONVERTED}
+     *
+     */
+    @Override
+    @EnforcePermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setTransferStatus(int subId, int status) {
+        setTransferStatus_enforcePermission();
+        if (mContext.checkCallingOrSelfPermission(
+                Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to"
+                    + "setTransferStatus");
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            mSubscriptionDatabaseManager.setTransferStatus(subId, status);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the satellite entitlement plmn list value in the subscription database.
+     *
+     * @param subId subscription id.
+     * @param satelliteEntitlementPlmnList satellite entitlement plmn list
+     */
+    public void setSatelliteEntitlementPlmnList(int subId,
+            @NonNull List<String> satelliteEntitlementPlmnList) {
+        try {
+            mSubscriptionDatabaseManager.setSatelliteEntitlementPlmnList(
+                    subId, satelliteEntitlementPlmnList);
+        } catch (IllegalArgumentException e) {
+            loge("setSatelliteEntitlementPlmnList: invalid subId=" + subId);
+        }
+    }
+
+    /**
+     * Get the satellite entitlement plmn list value from the subscription database.
+     *
+     * @param subId subscription id.
+     * @return satellite entitlement plmn list
+     */
+    @NonNull
+    public List<String> getSatelliteEntitlementPlmnList(int subId) {
+        SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(
+                subId);
+        if (subInfo == null) {
+            loge("getSatelliteEntitlementPlmnList: invalid subId=" + subId);
+            return new ArrayList<>();
+        }
+
+        return Arrays.stream(subInfo.getSatelliteEntitlementPlmns().split(",")).collect(
+                Collectors.toList());
+    }
+
+    /**
+     * Get the current calling package name.
+     * @return the current calling package name
+     */
+    @Nullable
+    private String getCurrentPackageName() {
+        if (mPackageManager == null) return null;
+        String[] callingUids = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+        return (callingUids == null) ? null : callingUids[0];
+    }
+
+    /**
+     * Make sure the device has required telephony feature
+     *
+     * @throws UnsupportedOperationException if the device does not have required telephony feature
+     */
+    private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+            @NonNull String methodName) {
+        if (callingPackage == null || mPackageManager == null) {
+            return;
+        }
+
+        if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+                || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+                Binder.getCallingUserHandle())) {
+            return;
+        }
+
+        if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY_SUBSCRIPTION)) {
+            throw new UnsupportedOperationException(
+                    methodName + " is unsupported without " + FEATURE_TELEPHONY_SUBSCRIPTION);
+        }
+    }
+
     /**
      * @return The logical SIM slot/sub mapping to string.
      */
@@ -3929,6 +4466,68 @@
     }
 
     /**
+     * @param mccMnc MccMnc value to check whether it supports non-terrestrial network or not.
+     * @return {@code true} if MCC/MNC is matched with in the device overlay key
+     * "config_satellite_sim_plmn_identifier", {@code false} otherwise.
+     */
+    private boolean isSatellitePlmn(@NonNull String mccMnc) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            log("isSatellitePlmn: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+
+        final int id = R.string.config_satellite_sim_plmn_identifier;
+        String overlayMccMnc = null;
+        try {
+            overlayMccMnc = mContext.getResources().getString(id);
+        } catch (Resources.NotFoundException ex) {
+            loge("isSatellitePlmn: id= " + id + ", ex=" + ex);
+        }
+        if (TextUtils.isEmpty(overlayMccMnc) && isMockModemAllowed()) {
+            log("isSatellitePlmn: Read config_satellite_sim_plmn_identifier from device config");
+            overlayMccMnc = DeviceConfig.getString(DeviceConfig.NAMESPACE_TELEPHONY,
+                    "config_satellite_sim_plmn_identifier", "");
+        }
+        log("isSatellitePlmn: overlayMccMnc=" + overlayMccMnc + ", mccMnc=" + mccMnc);
+        return TextUtils.equals(mccMnc, overlayMccMnc);
+    }
+
+    /**
+     * Checks and matches the service provider name (spn) with the device overlay config to
+     * determine whether non-terrestrial networks are supported.
+     * @param spn service provider name of the profile.
+     * @return {@code true} if the given spn is matched with the overlay key.
+     * "config_satellite_sim_spn_identifier", {@code false} otherwise.
+     */
+    private boolean isSatelliteSpn(@NonNull String spn) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            log("isSatelliteSpn: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+
+        final int id = R.string.config_satellite_sim_spn_identifier;
+        String overlaySpn = null;
+        try {
+            overlaySpn = mContext.getResources().getString(id);
+        } catch (Resources.NotFoundException ex) {
+            loge("isSatelliteSpn: id= " + id + ", ex=" + ex);
+        }
+        if (TextUtils.isEmpty(overlaySpn) && isMockModemAllowed()) {
+            log("isSatelliteSpn: Read config_satellite_sim_spn_identifier from device config");
+            overlaySpn = DeviceConfig.getString(DeviceConfig.NAMESPACE_TELEPHONY,
+                    "config_satellite_sim_spn_identifier", "");
+        }
+        log("isSatelliteSpn: overlaySpn=" + overlaySpn + ", spn=" + spn);
+        return TextUtils.equals(spn, overlaySpn);
+    }
+
+    private boolean isMockModemAllowed() {
+        boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false);
+        return (SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)
+                || SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false));
+    }
+
+    /**
      * Log debug messages.
      *
      * @param s debug messages
@@ -3957,15 +4556,6 @@
     }
 
     /**
-     * Log verbose messages.
-     *
-     * @param s verbose messages
-     */
-    private void logv(@NonNull String s) {
-        Rlog.v(LOG_TAG, s);
-    }
-
-    /**
      * Dump the state of {@link SubscriptionManagerService}.
      *
      * @param fd File descriptor
@@ -3999,6 +4589,7 @@
             pw.println("activeDataSubId=" + getActiveDataSubscriptionId());
             pw.println("defaultSmsSubId=" + getDefaultSmsSubId());
             pw.println("areAllSubscriptionsLoaded=" + areAllSubscriptionsLoaded());
+            pw.println("mUserIdToAvailableSubs=" + mUserIdToAvailableSubs);
             pw.println();
             for (int i = 0; i < mSimState.length; i++) {
                 pw.println("mSimState[" + i + "]="
diff --git a/src/java/com/android/internal/telephony/uicc/SimPhonebookRecordCache.java b/src/java/com/android/internal/telephony/uicc/SimPhonebookRecordCache.java
index 149e605..b1fc473 100644
--- a/src/java/com/android/internal/telephony/uicc/SimPhonebookRecordCache.java
+++ b/src/java/com/android/internal/telephony/uicc/SimPhonebookRecordCache.java
@@ -446,6 +446,7 @@
                 notifyAdnLoadingWaiters();
                 tryFireUpdatePendingList();
             } else {
+                notifyAdnLoadingWaiters();
                 logd("ADN capacity is invalid");
             }
             mIsInitialized.set(true); // Let's say the whole process is ready
@@ -455,6 +456,9 @@
                 mIsCacheInvalidated.set(false);
                 notifyAdnLoadingWaiters();
                 tryFireUpdatePendingList();
+            } else if (!newCapacity.isSimValid()) {
+                mIsCacheInvalidated.set(false);
+                notifyAdnLoadingWaiters();
             } else if (!mIsUpdateDone && !newCapacity.isSimEmpty()) {
                 invalidateSimPbCache();
                 fillCacheWithoutWaiting();
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 566bec2..0459bf6 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -597,17 +597,17 @@
                         log("Received EVENT_RADIO_AVAILABLE/EVENT_RADIO_ON, calling "
                                 + "getIccCardStatus");
                     }
-                    mCis[phoneId].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE,
-                            phoneId));
                     // slot status should be the same on all RILs; request it only for phoneId 0
                     if (phoneId == 0) {
                         if (DBG) {
                             log("Received EVENT_RADIO_AVAILABLE/EVENT_RADIO_ON for phoneId 0, "
-                                    + "calling getIccSlotsStatus");
+                                    + "calling getSimSlotsStatus");
                         }
                         mRadioConfig.getSimSlotsStatus(obtainMessage(EVENT_GET_SLOT_STATUS_DONE,
                                 phoneId));
                     }
+                    mCis[phoneId].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE,
+                            phoneId));
                     break;
                 case EVENT_GET_ICC_STATUS_DONE:
                     if (DBG) log("Received EVENT_GET_ICC_STATUS_DONE");
@@ -804,12 +804,14 @@
             UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
             int slotId = UiccController.getInstance().getSlotIdFromPhoneId(phoneId);
             intent.putExtra(PhoneConstants.SLOT_KEY, slotId);
+            int portIndex = -1;
             if (slot != null) {
-                intent.putExtra(PhoneConstants.PORT_KEY, slot.getPortIndexFromPhoneId(phoneId));
+                portIndex = slot.getPortIndexFromPhoneId(phoneId);
+                intent.putExtra(PhoneConstants.PORT_KEY, portIndex);
             }
             Rlog.d(LOG_TAG, "Broadcasting intent ACTION_SIM_CARD_STATE_CHANGED "
                     + TelephonyManager.simStateToString(state) + " for phone: " + phoneId
-                    + " slot: " + slotId + " port: " + slot.getPortIndexFromPhoneId(phoneId));
+                    + " slot: " + slotId + " port: " + portIndex);
             mContext.sendBroadcast(intent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
             TelephonyMetrics.getInstance().updateSimState(phoneId, state);
         }
@@ -995,6 +997,12 @@
                             return;
                         }
 
+                        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+                            Rlog.e(LOG_TAG, "updateSimState: Cannot update carrier services. "
+                                    + "Invalid phone id " + phoneId);
+                            return;
+                        }
+
                         // At this point, the SIM state must be a final state (meaning we won't
                         // get more SIM state updates). So resolve the carrier id and update the
                         // carrier services.
@@ -1037,11 +1045,6 @@
             slotId = index;
         }
 
-        if (!mCis[0].supportsEid()) {
-            // we will never get EID from the HAL, so set mDefaultEuiccCardId to UNSUPPORTED_CARD_ID
-            if (DBG) log("eid is not supported");
-            mDefaultEuiccCardId = UNSUPPORTED_CARD_ID;
-        }
         mPhoneIdToSlotId[index] = slotId;
 
         if (VDBG) logPhoneIdToSlotIdMapping();
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPort.java b/src/java/com/android/internal/telephony/uicc/UiccPort.java
index d89eab1..fd8f1c4 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPort.java
@@ -29,6 +29,8 @@
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IccLogicalChannelRequest;
 import com.android.internal.telephony.TelephonyComponentFactory;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -39,6 +41,7 @@
 public class UiccPort {
     protected static final String LOG_TAG = "UiccPort";
     protected static final boolean DBG = true;
+    private static @NonNull FeatureFlags sFlags = new FeatureFlagsImpl();
 
     // The lock object is created by UiccSlot that owns this UiccCard - this is to share the lock
     // between UiccSlot, UiccCard, EuiccCard, UiccPort, EuiccPort and UiccProfile for now.
@@ -80,7 +83,7 @@
             if (mUiccProfile == null) {
                 mUiccProfile = TelephonyComponentFactory.getInstance()
                         .inject(UiccProfile.class.getName()).makeUiccProfile(
-                                mContext, mCi, ics, mPhoneId, uiccCard, mLock);
+                                mContext, mCi, ics, mPhoneId, uiccCard, mLock, sFlags);
             } else {
                 mUiccProfile.update(mContext, mCi, ics);
             }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 83db022..0457971 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -20,6 +20,7 @@
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_FAILURE;
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_SUCCESS;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.usage.UsageStatsManager;
@@ -65,6 +66,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.cat.CatService;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
@@ -103,6 +105,7 @@
 
     private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
 
+    private final @NonNull FeatureFlags mFlags;
     // The lock object is created by UiccSlot that owns the UiccCard that owns this UiccProfile.
     // This is to share the lock between UiccSlot, UiccCard and UiccProfile for now.
     private final Object mLock;
@@ -326,8 +329,9 @@
     };
 
     public UiccProfile(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId,
-            UiccCard uiccCard, Object lock) {
+            UiccCard uiccCard, Object lock, @NonNull FeatureFlags flags) {
         if (DBG) log("Creating profile");
+        mFlags = flags;
         mLock = lock;
         mUiccCard = uiccCard;
         mPhoneId = phoneId;
diff --git a/testing/Android.bp b/testing/Android.bp
index 3c100d8..903b98e 100644
--- a/testing/Android.bp
+++ b/testing/Android.bp
@@ -16,7 +16,7 @@
         "guava",
         "junit",
         "mockito-target-minus-junit4",
-        "truth-prebuilt",
+        "truth",
     ],
 
     sdk_version: "test_current",
diff --git a/tests/telephonytests/Android.bp b/tests/telephonytests/Android.bp
index 2aa446d..8547581 100644
--- a/tests/telephonytests/Android.bp
+++ b/tests/telephonytests/Android.bp
@@ -38,9 +38,11 @@
         "platform-test-annotations",
         "services.core",
         "services.net",
-        "truth-prebuilt",
+        "truth",
         "testables",
-        "platform-compat-test-rules"
+        "platform-compat-test-rules",
+        "flag-junit",
+        "telephony_flags_core_java_lib",
     ],
 
     jarjar_rules: ":jarjar-rules-telephony-tests",
diff --git a/tests/telephonytests/src/android/telephony/BinderCacheManagerTest.java b/tests/telephonytests/src/android/telephony/BinderCacheManagerTest.java
index e5b5f3a..ede37f9 100644
--- a/tests/telephonytests/src/android/telephony/BinderCacheManagerTest.java
+++ b/tests/telephonytests/src/android/telephony/BinderCacheManagerTest.java
@@ -27,8 +27,8 @@
 
 import android.os.IBinder;
 import android.os.IInterface;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
diff --git a/tests/telephonytests/src/android/telephony/SmsMessageTest.java b/tests/telephonytests/src/android/telephony/SmsMessageTest.java
index 2bd865d..5f696b1 100644
--- a/tests/telephonytests/src/android/telephony/SmsMessageTest.java
+++ b/tests/telephonytests/src/android/telephony/SmsMessageTest.java
@@ -16,12 +16,12 @@
 
 package android.telephony;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.SmsConstants;
 
-import static org.junit.Assert.assertEquals;
-
 import org.junit.Test;
 
 public class SmsMessageTest {
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsCompatTests.java b/tests/telephonytests/src/android/telephony/ims/ImsCompatTests.java
index 414cb47..eebcf8f 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsCompatTests.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsCompatTests.java
@@ -22,8 +22,8 @@
 import static org.mockito.Mockito.verify;
 
 import android.telephony.ims.aidl.IImsCallSessionListener;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.ims.internal.IImsMMTelFeature;
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java b/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
index 80167d6..2dc0b31 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
@@ -31,8 +31,8 @@
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
@@ -48,7 +48,7 @@
 @RunWith(AndroidJUnit4.class)
 public class ImsFeatureTest {
     // Public for Mockito testing
-    public class CapabilityCallback extends IImsCapabilityCallback.Stub {
+    public static class CapabilityCallback extends IImsCapabilityCallback.Stub {
 
         @Override
         public void onQueryCapabilityConfiguration(int capability, int radioTech, boolean enabled)
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsMmTelManagerTests.java b/tests/telephonytests/src/android/telephony/ims/ImsMmTelManagerTests.java
index 337e296..afc0d50 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsMmTelManagerTests.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsMmTelManagerTests.java
@@ -26,7 +26,8 @@
 import android.telephony.BinderCacheManager;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.TelephonyTest;
@@ -41,7 +42,7 @@
     ITelephony mMockTelephonyInterface;
     BinderCacheManager<ITelephony> mBinderCache;
 
-    public class LocalCallback extends ImsMmTelManager.RegistrationCallback {
+    public static class LocalCallback extends ImsMmTelManager.RegistrationCallback {
         int mRegResult = -1;
 
         @Override
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java b/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java
index 56ce6bc..b054a49 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java
@@ -35,9 +35,9 @@
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java b/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java
index cb3ca89..d9d387c 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java
@@ -39,8 +39,8 @@
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsStateCallbackTest.java b/tests/telephonytests/src/android/telephony/ims/ImsStateCallbackTest.java
index 9a9713d..9321a1b 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsStateCallbackTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsStateCallbackTest.java
@@ -28,7 +28,8 @@
 
 import android.telephony.BinderCacheManager;
 import android.telephony.ims.aidl.IImsRcsController;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.IImsStateCallback;
 import com.android.internal.telephony.ITelephony;
diff --git a/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java b/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
index b002b35..aa5db68 100644
--- a/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
+++ b/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
@@ -35,8 +35,8 @@
 import android.telephony.ims.aidl.IImsMmTelFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsCallSessionImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.ims.internal.IImsCallSession;
diff --git a/tests/telephonytests/src/android/telephony/ims/RcsConfigTest.java b/tests/telephonytests/src/android/telephony/ims/RcsConfigTest.java
index a81e93f..4889187 100644
--- a/tests/telephonytests/src/android/telephony/ims/RcsConfigTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/RcsConfigTest.java
@@ -30,9 +30,9 @@
 import android.telephony.ims.RcsConfig;
 import android.telephony.ims.RcsConfig.Characteristic;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.FakeTelephonyProvider;
 
diff --git a/tests/telephonytests/src/android/telephony/ims/RcsFeatureTest.java b/tests/telephonytests/src/android/telephony/ims/RcsFeatureTest.java
index fa88d71..3c5ccad 100644
--- a/tests/telephonytests/src/android/telephony/ims/RcsFeatureTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/RcsFeatureTest.java
@@ -25,9 +25,9 @@
 import android.telephony.ims.feature.RcsFeature;
 import android.telephony.ims.stub.CapabilityExchangeEventListener;
 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.telephony.ims.ImsTestBase;
diff --git a/tests/telephonytests/src/android/telephony/mbms/MbmsReceiverTest.java b/tests/telephonytests/src/android/telephony/mbms/MbmsReceiverTest.java
index b93d400..6debad3 100644
--- a/tests/telephonytests/src/android/telephony/mbms/MbmsReceiverTest.java
+++ b/tests/telephonytests/src/android/telephony/mbms/MbmsReceiverTest.java
@@ -18,8 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.test.suitebuilder.annotation.SmallTest;
-
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ATResponseParserTest.java b/tests/telephonytests/src/com/android/internal/telephony/ATResponseParserTest.java
index 81727e4..e53e813 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ATResponseParserTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ATResponseParserTest.java
@@ -16,8 +16,9 @@
 
 package com.android.internal.telephony;
 
+import androidx.test.filters.SmallTest;
+
 import junit.framework.TestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 
 public class ATResponseParserTest extends TestCase {
     @SmallTest
diff --git a/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java b/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java
index 8fe809c..a442b0d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java
@@ -19,7 +19,8 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.internal.telephony.uicc.IccUtils;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallAttributesTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallAttributesTest.java
index 7a85926..62ff484 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CallAttributesTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallAttributesTest.java
@@ -23,7 +23,8 @@
 import android.telephony.CallQuality;
 import android.telephony.PreciseCallState;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 /**
  * Simple GTS test verifying the parceling and unparceling of CallAttributes.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java
index 62b2a45..82bbc18 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java
@@ -35,10 +35,11 @@
 import android.os.Message;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallQualityTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallQualityTest.java
index 1a6bb51..c4ce498 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CallQualityTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallQualityTest.java
@@ -20,8 +20,8 @@
 
 import android.os.Parcel;
 import android.telephony.CallQuality;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallStateExceptionTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallStateExceptionTest.java
index 7b6c2b7..1481633 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CallStateExceptionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallStateExceptionTest.java
@@ -17,7 +17,7 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import org.junit.After;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java
index 4e319a1..88dc591 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java
@@ -25,7 +25,8 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java
index eb18adb..184820c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java
@@ -51,10 +51,11 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierActionAgentTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierActionAgentTest.java
index 4f91994..ed45cd5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierActionAgentTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierActionAgentTest.java
@@ -31,10 +31,11 @@
 import android.provider.Settings;
 import android.provider.Telephony;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
index 583a147..2a66a5f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
@@ -27,11 +27,11 @@
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
index 40e1821..07482e0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
@@ -36,17 +36,21 @@
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 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.Mockito;
 
 import java.security.PublicKey;
 import java.text.SimpleDateFormat;
@@ -87,18 +91,19 @@
                     + "\"public-key\": \"" + CERT + "\"}]}";
 
     private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
-
+    private FeatureFlags mFeatureFlags;
     @Before
     public void setUp() throws Exception {
         logd("CarrierActionAgentTest +Setup!");
         super.setUp(getClass().getSimpleName());
         mBundle = mContextFixture.getCarrierConfigBundle();
         when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(mBundle);
-
+        when(mUserManager.isUserUnlocked()).thenReturn(true);
         // Capture listener to emulate the carrier config change notification used later
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        mCarrierKeyDM = new CarrierKeyDownloadManager(mPhone);
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
+        mCarrierKeyDM = new CarrierKeyDownloadManager(mPhone, mFeatureFlags);
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
         mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
@@ -342,7 +347,7 @@
      **/
     @Test
     @SmallTest
-    public void testCarrierConfigChanged() {
+    public void testCarrierConfigChangedWithUserUnlocked() {
         CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         int slotId = mPhone.getPhoneId();
@@ -360,6 +365,57 @@
         assertEquals(1, mCarrierKeyDM.mCarrierId);
     }
 
+    @Test
+    @SmallTest
+    public void testCarrierConfigChangedWithUserLocked() {
+        when(mUserManager.isUserUnlocked()).thenReturn(false);
+        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        int slotId = mPhone.getPhoneId();
+        PersistableBundle bundle = carrierConfigManager.getConfigForSubId(slotId);
+        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);
+        mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+        processAllMessages();
+        assertNull(mCarrierKeyDM.mMccMncForDownload);
+        assertEquals(0, mCarrierKeyDM.mCarrierId);
+    }
+
+    @Test
+    @SmallTest
+    public void testUserLockedAfterCarrierConfigChanged() {
+        // User is locked at beginning
+        when(mUserManager.isUserUnlocked()).thenReturn(false);
+        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        int slotId = mPhone.getPhoneId();
+        PersistableBundle bundle = carrierConfigManager.getConfigForSubId(slotId);
+        bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
+        bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
+
+        // Carrier config change received
+        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
+        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+        processAllMessages();
+
+        // User unlocked event received
+        Intent mIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
+        mContext.sendBroadcast(mIntent);
+        when(mUserManager.isUserUnlocked()).thenReturn(true);
+        processAllMessages();
+
+        assertEquals("310260", mCarrierKeyDM.mMccMncForDownload);
+        assertEquals(1, mCarrierKeyDM.mCarrierId);
+    }
+
     /**
      * Tests notifying carrier config change from listener with an empty key.
      * Verify that the carrier keys are removed if IMSI_KEY_DOWNLOAD_URL_STRING is null.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
index 453dbd6..a3fcd4e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
@@ -31,10 +31,11 @@
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierRestrictionRulesTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierRestrictionRulesTest.java
index fdefa1d..7e86ce3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierRestrictionRulesTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierRestrictionRulesTest.java
@@ -16,11 +16,16 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT;
 import android.os.Parcel;
 import android.service.carrier.CarrierIdentifier;
+import android.telephony.CarrierInfo;
 import android.telephony.CarrierRestrictionRules;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -340,4 +345,101 @@
         List<Boolean> expected = Arrays.asList(false, false, false);
         assertTrue(result.equals(expected));
     }
+
+    @Test
+    public void testBuilderAllowedAndExcludedCarrierInfos() {
+        List<CarrierInfo> allowedCarriers = new ArrayList<>();
+        allowedCarriers.add(new CarrierInfo(MCC1, MNC1, null, null, null, null, null, null, null));
+        allowedCarriers.add(new CarrierInfo(MCC2, MNC2, null, null, null, null, null, null, null));
+
+        List<CarrierInfo> excludedCarriers = new ArrayList<>();
+        excludedCarriers.add(new CarrierInfo(MCC2, MNC2, null, null, GID1, null, null, null, null));
+
+        CarrierRestrictionRules rules =
+                CarrierRestrictionRules.newBuilder().setAllowedCarrierInfo(
+                        allowedCarriers).setExcludedCarrierInfo(excludedCarriers).build();
+
+        assertEquals(false, rules.isAllCarriersAllowed());
+        assertTrue(rules.getAllowedCarriersInfoList().equals(allowedCarriers));
+        assertTrue(rules.getExcludedCarriersInfoList().equals(excludedCarriers));
+        assertEquals(CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED,
+                rules.getDefaultCarrierRestriction());
+    }
+
+    @Test
+    public void testBuilderAllowedAndExcludedCarrierInfoWithEplmn() {
+        List<String> plmns = new ArrayList<>();
+        plmns.add("**1" + "," + "123");
+        plmns.add("2*1" + "," + "1*3");
+
+        List<String> plmns2 = new ArrayList<>();
+        plmns2.add("**1" + "," + "123");
+        plmns2.add("2*1" + "," + "1*3");
+        plmns2.add("2**" + "," + "*");
+
+        List<CarrierInfo> allowedCarriers = new ArrayList<>();
+        allowedCarriers.add(new CarrierInfo(MCC1, MNC1, null, null, null, null, null, null, plmns));
+        allowedCarriers.add(
+                new CarrierInfo(MCC2, MNC2, null, null, null, null, null, null, plmns2));
+
+        List<CarrierInfo> excludedCarriers = new ArrayList<>();
+        excludedCarriers.add(new CarrierInfo(MCC2, MNC2, null, null, GID1, null, null, null, null));
+
+        CarrierRestrictionRules rules =
+                CarrierRestrictionRules.newBuilder().setAllowedCarrierInfo(
+                        allowedCarriers).setExcludedCarrierInfo(excludedCarriers).build();
+
+        assertEquals(false, rules.isAllCarriersAllowed());
+        assertTrue(rules.getAllowedCarriersInfoList().equals(allowedCarriers));
+        assertTrue(rules.getExcludedCarriersInfoList().equals(excludedCarriers));
+        assertTrue(rules.getAllowedCarriersInfoList().get(0).getEhplmn().equals(plmns));
+        assertTrue(rules.getAllowedCarriersInfoList().get(1).getEhplmn().equals(plmns2));
+        assertEquals(CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED,
+                rules.getDefaultCarrierRestriction());
+    }
+
+    @Test
+    public void testBuilderAllowedAndExcludedCarrierInfoWithNullEplmn() {
+        List<String> plmns = new ArrayList<>();
+        List<CarrierInfo> allowedCarriers = new ArrayList<>();
+        allowedCarriers.add(new CarrierInfo(MCC1, MNC1, null, null, null, null, null, null, plmns));
+
+        List<CarrierInfo> excludedCarriers = new ArrayList<>();
+        excludedCarriers.add(new CarrierInfo(MCC2, MNC2, null, null, GID1, null, null, null, null));
+
+        CarrierRestrictionRules rules =
+                CarrierRestrictionRules.newBuilder().setAllowedCarrierInfo(
+                        allowedCarriers).setExcludedCarrierInfo(excludedCarriers).build();
+
+        assertEquals(false, rules.isAllCarriersAllowed());
+        assertTrue(rules.getAllowedCarriersInfoList().equals(allowedCarriers));
+        assertTrue(rules.getExcludedCarriersInfoList().equals(excludedCarriers));
+        assertTrue(rules.getAllowedCarriersInfoList().get(0).getEhplmn().equals(plmns));
+        assertEquals(CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED,
+                rules.getDefaultCarrierRestriction());
+    }
+
+    @Test
+    public void testBuilderAllowedAndExcludedCarrierInfoWithSimPolicy() {
+        List<CarrierInfo> allowedCarriers = new ArrayList<>();
+        allowedCarriers.add(new CarrierInfo(MCC1, MNC1, null, null, null, null, null, null, null));
+
+        List<CarrierInfo> excludedCarriers = new ArrayList<>();
+        excludedCarriers.add(new CarrierInfo(MCC2, MNC2, null, null, GID1, null, null, null, null));
+
+        CarrierRestrictionRules rules =
+                CarrierRestrictionRules.newBuilder().
+                        setAllowedCarrierInfo(allowedCarriers).
+                        setExcludedCarrierInfo(excludedCarriers).
+                        setDefaultCarrierRestriction(
+                                CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED).
+                        setMultiSimPolicy(MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT).build();
+
+        assertEquals(true, rules.isAllCarriersAllowed());
+        assertTrue(rules.getAllowedCarriersInfoList().equals(allowedCarriers));
+        assertTrue(rules.getExcludedCarriersInfoList().equals(excludedCarriers));
+        assertEquals(MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT, rules.getMultiSimPolicy());
+        assertEquals(CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED,
+                rules.getDefaultCarrierRestriction());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceBindHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceBindHelperTest.java
index 8ce11ae..331fcba 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceBindHelperTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceBindHelperTest.java
@@ -27,10 +27,11 @@
 
 import android.os.Message;
 import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
index dfb91a5..85c73a9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static com.android.internal.telephony.CarrierServiceStateTracker.ACTION_NEVER_ASK_AGAIN;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
@@ -26,22 +28,26 @@
 import static org.mockito.Mockito.isA;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -78,11 +84,12 @@
         mBundle = mContextFixture.getCarrierConfigBundle();
         when(mPhone.getSubId()).thenReturn(SUB_ID);
         when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(mBundle);
+        doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_WATCH);
 
         // Capture listener to emulate the carrier config change notification used later
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        mCarrierSST = new CarrierServiceStateTracker(mPhone, mSST);
+        mCarrierSST = new CarrierServiceStateTracker(mPhone, mSST, mFeatureFlags);
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
         mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
@@ -271,4 +278,54 @@
         verify(mNotificationManager, never()).cancel(
                 CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG, SUB_ID);
     }
+
+    /**
+     * Verify the WIFI emergency calling notification is silenced if the user requests (via a
+     * simulated notification action)
+     */
+    @Test
+    @SmallTest
+    public void testEmergencyNotificationBehaviorWhenSilenced() {
+        when(mFeatureFlags.stopSpammingEmergencyNotification()).thenReturn(true);
+        logd(LOG_TAG + ":testEmergencyNotificationBehaviorWhenSilenced()");
+        sendMessageOnHandler(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
+
+        // verify the notification was sent
+        verify(mNotificationManager, times(1)).notify(
+                eq(CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG),
+                eq(SUB_ID), isA(Notification.class));
+
+        // simulate the user clicking the "Do Not Show Again" button on the notification
+        mCarrierSST.mActionReceiver.onReceive(mContext, new Intent(ACTION_NEVER_ASK_AGAIN));
+
+        // resend the msg to trigger the notification to be posted
+        sendMessageOnHandler(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
+
+        // verify the notification was sent
+        verify(mNotificationManager, times(1)).notify(
+                eq(CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG),
+                eq(SUB_ID), isA(Notification.class));
+    }
+
+
+    /** Verifies notification map is empty when device is watch. */
+    @Test
+    @SmallTest
+    public void testNotificationMapWhenDeviceIsWatch() {
+        doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_WATCH);
+
+        CarrierServiceStateTracker tracker = new CarrierServiceStateTracker(mPhone, mSST,
+                mFeatureFlags);
+
+        assertTrue(tracker.getNotificationTypeMap().isEmpty());
+    }
+
+    private void sendMessageOnHandler(int messageWhat) {
+        Message notificationMsg = mSpyCarrierSST.obtainMessage(messageWhat, null);
+        doReturn(true).when(mSpyCarrierSST).evaluateSendingMessage(any());
+        doReturn(0).when(mSpyCarrierSST).getDelay(any());
+        doReturn(mNotificationManager).when(mSpyCarrierSST).getNotificationManager(any());
+        mSpyCarrierSST.handleMessage(notificationMsg);
+        processAllMessages();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java
index 00adc39..e68d065 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java
@@ -37,7 +37,8 @@
 import android.service.carrier.ICarrierMessagingCallback;
 import android.service.carrier.ICarrierMessagingService;
 import android.service.carrier.MessagePdu;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierSignalAgentTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierSignalAgentTest.java
index 4dfadd2..7710648 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierSignalAgentTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierSignalAgentTest.java
@@ -47,10 +47,11 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java
index 40be490..6e6d4e4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java
@@ -42,6 +42,7 @@
 import android.testing.TestableLooper;
 
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
 import org.junit.After;
@@ -49,6 +50,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -60,13 +63,16 @@
 
     private CommandsInterface mSpyCi;
     private CellBroadcastConfigTracker mTracker;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
         mSpyCi = spy(mSimulatedCommands);
         mPhone = new GsmCdmaPhone(mContext, mSpyCi, mNotifier, true, 0,
-            PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager);
+            PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager,
+                mFeatureFlags);
         mTracker = CellBroadcastConfigTracker.make(mPhone, mPhone, true);
         mPhone.mCellBroadcastConfigTracker = mTracker;
         processAllMessages();
@@ -410,6 +416,26 @@
     }
 
     @Test
+    public void testClearCellBroadcastConfigOnModemReset() {
+        List<CellBroadcastIdRange> ranges = new ArrayList<>();
+        ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true));
+
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r));
+        processAllMessages();
+
+        assertEquals(mPhone.getCellBroadcastIdRanges(), ranges);
+
+        Message m = mTracker.obtainMessage(CellBroadcastConfigTracker.EVENT_RADIO_RESET);
+        AsyncResult.forMessage(m);
+        m.sendToTarget();
+        processAllMessages();
+
+        // Verify the config is reset
+        assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty());
+    }
+
+    @Test
     public void testClearCellBroadcastConfigOnSubscriptionChanged() {
         List<CellBroadcastIdRange> ranges = new ArrayList<>();
         ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityCdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityCdmaTest.java
index 133b4ba..c7668ca 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityCdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityCdmaTest.java
@@ -21,7 +21,8 @@
 import android.os.Parcel;
 import android.telephony.CellIdentityCdma;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 /** Unit tests for {@link CellIdentityCdma}. */
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
index 9d39b07..cbdf3b7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
@@ -19,7 +19,8 @@
 import android.os.Parcel;
 import android.telephony.CellIdentityGsm;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import java.util.Collections;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
index 3eb8d21..9b05b67 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
@@ -20,7 +20,8 @@
 import android.telephony.CellIdentityLte;
 import android.telephony.CellInfo;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
index d1da119..3926c03 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
@@ -20,7 +20,8 @@
 import android.telephony.CellIdentityTdscdma;
 import android.telephony.CellInfo;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 /** Unit tests for {@link CellIdentityTdscdma}. */
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTest.java
index 0ceed1c..0be1ae4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTest.java
@@ -26,7 +26,8 @@
 import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
index 1e472fb..62caef4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
@@ -19,7 +19,8 @@
 import android.os.Parcel;
 import android.telephony.CellIdentityWcdma;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import java.util.Collections;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthCdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthCdmaTest.java
index 063fb3c..3a20497 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthCdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthCdmaTest.java
@@ -23,7 +23,8 @@
 import android.os.Parcel;
 import android.telephony.CellSignalStrengthCdma;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 /** Unit tests for {@link CellSignalStrengthCdma}. */
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthTdscdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthTdscdmaTest.java
index 8b5c3d5..1c17fbe 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthTdscdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthTdscdmaTest.java
@@ -24,7 +24,8 @@
 import android.telephony.CellInfo;
 import android.telephony.CellSignalStrengthTdscdma;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 /** Unit tests for {@link CellSignalStrengthCdma}. */
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthWcdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthWcdmaTest.java
index 8323ba3..2a00bdb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthWcdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthWcdmaTest.java
@@ -26,7 +26,8 @@
 import android.telephony.CellInfo;
 import android.telephony.CellSignalStrengthWcdma;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 /** Unit tests for {@link CellSignalStrengthCdma}. */
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
index 8cd5dc3..ebf1324 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
@@ -39,11 +39,13 @@
 import android.telephony.NetworkServiceCallback;
 import android.telephony.NrVopsSupportInfo;
 import android.telephony.ServiceState;
+import android.telephony.SmsManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.VopsSupportInfo;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
 
@@ -92,6 +94,9 @@
         int dds = SubscriptionManager.getDefaultDataSubscriptionId();
         doReturn(dds).when(mPhone).getSubId();
 
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                SmsManager.MMS_CONFIG_MMS_ENABLED, false);
+
         logd("CellularNetworkServiceTest -Setup!");
     }
 
@@ -510,4 +515,48 @@
                         new CellIdentityWcdma(),
                         mPhone.getCarrierId()));
     }
+
+    @Test
+    public void testGetAvailableServices_withMmsEnabled() {
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                SmsManager.MMS_CONFIG_MMS_ENABLED, true);
+
+        VopsSupportInfo lteVopsSupportInfo =
+                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
+        int voiceRegState = NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+        int voiceRadioTech = ServiceState.RIL_RADIO_TECHNOLOGY_UMTS;
+        int reasonForDenial = 0;
+        int maxDataCalls = 4;
+
+        mSimulatedCommands.setVoiceRegState(voiceRegState);
+        mSimulatedCommands.setVoiceRadioTech(voiceRadioTech);
+        mSimulatedCommands.mReasonForDenial = reasonForDenial;
+        mSimulatedCommands.mMaxDataCalls = maxDataCalls;
+        mSimulatedCommands.notifyNetworkStateChanged();
+
+        int domain = NetworkRegistrationInfo.DOMAIN_PS;
+        List<Integer> availableServices = Arrays.asList(
+                NetworkRegistrationInfo.SERVICE_TYPE_DATA,
+                NetworkRegistrationInfo.SERVICE_TYPE_MMS);
+        try {
+            mBinder.requestNetworkRegistrationInfo(0, domain, mCallback);
+        } catch (RemoteException e) {
+            assertTrue(false);
+        }
+
+        NetworkRegistrationInfo expectedState = new NetworkRegistrationInfo(
+                domain, AccessNetworkConstants.TRANSPORT_TYPE_WWAN, voiceRegState,
+                ServiceState.rilRadioTechnologyToNetworkType(voiceRadioTech), reasonForDenial,
+                false, availableServices, null, "", maxDataCalls,
+                false, false, false, lteVopsSupportInfo);
+
+        try {
+            verify(mCallback, timeout(1000).times(1))
+                    .onRequestNetworkRegistrationInfoComplete(
+                            eq(NetworkServiceCallback.RESULT_SUCCESS), eq(expectedState));
+        } catch (RemoteException e) {
+            assertTrue(false);
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index ea19b62..bef8944 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -34,6 +34,7 @@
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.UiModeManager;
+import android.app.admin.DevicePolicyManager;
 import android.app.usage.UsageStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -108,7 +109,7 @@
  * Controls a test {@link Context} as would be provided by the Android framework to an
  * {@code Activity}, {@code Service} or other system-instantiated component.
  *
- * Contains Fake<Component> classes like FakeContext for components that require complex and
+ * Contains {@code Fake<Component>} classes like FakeContext for components that require complex and
  * reusable stubbing. Others can be mocked using Mockito functions in tests or constructor/public
  * methods of this class.
  */
@@ -117,7 +118,6 @@
     public static final String PERMISSION_ENABLE_ALL = "android.permission.STUB_PERMISSION";
 
     public static class FakeContentProvider extends MockContentProvider {
-        private String[] mColumns = {"name", "value"};
         private HashMap<String, String> mKeyValuePairs = new HashMap<String, String>();
         private int mNumKeyValuePairs = 0;
         private HashMap<String, String> mFlags = new HashMap<>();
@@ -311,6 +311,8 @@
                     return mNetworkPolicyManager;
                 case Context.TELEPHONY_IMS_SERVICE:
                     return mImsManager;
+                case Context.DEVICE_POLICY_SERVICE:
+                    return mDevicePolicyManager;
                 default:
                     return null;
             }
@@ -358,6 +360,8 @@
                 return Context.EUICC_SERVICE;
             } else if (serviceClass == AlarmManager.class) {
                 return Context.ALARM_SERVICE;
+            } else if (serviceClass == DevicePolicyManager.class) {
+                return Context.DEVICE_POLICY_SERVICE;
             }
             return super.getSystemServiceName(serviceClass);
         }
@@ -732,6 +736,7 @@
     private final VcnManager mVcnManager = mock(VcnManager.class);
     private final NetworkPolicyManager mNetworkPolicyManager = mock(NetworkPolicyManager.class);
     private final ImsManager mImsManager = mock(ImsManager.class);
+    private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
     private final Configuration mConfiguration = new Configuration();
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private final SharedPreferences mSharedPreferences = PreferenceManager
@@ -747,18 +752,14 @@
         doAnswer(new Answer<List<ResolveInfo>>() {
             @Override
             public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
-                return doQueryIntentServices(
-                        (Intent) invocation.getArguments()[0],
-                        (Integer) invocation.getArguments()[1]);
+                return doQueryIntentServices((Intent) invocation.getArguments()[0]);
             }
         }).when(mPackageManager).queryIntentServices((Intent) any(), anyInt());
 
         doAnswer(new Answer<List<ResolveInfo>>() {
             @Override
             public List<ResolveInfo> answer(InvocationOnMock invocation) throws Throwable {
-                return doQueryIntentServices(
-                        (Intent) invocation.getArguments()[0],
-                        (Integer) invocation.getArguments()[1]);
+                return doQueryIntentServices((Intent) invocation.getArguments()[0]);
             }
         }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), any());
 
@@ -766,6 +767,7 @@
             doReturn(mPackageInfo).when(mPackageManager).getPackageInfo(nullable(String.class),
                     anyInt());
         } catch (NameNotFoundException e) {
+            Log.d(TAG, "NameNotFoundException: e=" + e);
         }
 
         doAnswer((Answer<Boolean>)
@@ -775,7 +777,7 @@
         try {
             doReturn(mResources).when(mPackageManager).getResourcesForApplication(anyString());
         } catch (NameNotFoundException ex) {
-            Log.d(TAG, "NameNotFoundException: " + ex);
+            Log.d(TAG, "NameNotFoundException: ex=" + ex);
         }
 
         doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
@@ -851,7 +853,7 @@
         mMockBindingFailureForPackage.add(packageName);
     }
 
-    private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
+    private List<ResolveInfo> doQueryIntentServices(Intent intent) {
         List<ResolveInfo> result = new ArrayList<ResolveInfo>();
         for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
             ResolveInfo resolveInfo = new ResolveInfo();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index 2f4182a..d27ab98 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellInfo;
@@ -32,14 +33,18 @@
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
 
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -51,6 +56,8 @@
 
     private DefaultPhoneNotifier mDefaultPhoneNotifierUT;
 
+    private FeatureFlags mFeatureFlags;
+
     // Mocked classes
     SignalStrength mSignalStrength;
     CellInfo mCellInfo;
@@ -66,13 +73,14 @@
         super.setUp(getClass().getSimpleName());
         mSignalStrength = mock(SignalStrength.class);
         mCellInfo = mock(CellInfo.class);
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
         mForeGroundCall = mock(GsmCdmaCall.class);
         mBackGroundCall = mock(GsmCdmaCall.class);
         mRingingCall = mock(GsmCdmaCall.class);
         mImsForeGroundCall = mock(ImsPhoneCall.class);
         mImsBackGroundCall = mock(ImsPhoneCall.class);
         mImsRingingCall = mock(ImsPhoneCall.class);
-        mDefaultPhoneNotifierUT = new DefaultPhoneNotifier(mContext);
+        mDefaultPhoneNotifierUT = new DefaultPhoneNotifier(mContext, mFeatureFlags);
     }
 
     @After
@@ -94,6 +102,7 @@
 
     @Test @SmallTest
     public void testNotifyDataActivity() throws Exception {
+        when(mFeatureFlags.notifyDataActivityChangedWithSlot()).thenReturn(false);
         //mock data activity state
         doReturn(TelephonyManager.DATA_ACTIVITY_NONE).when(mPhone).getDataActivityState();
         mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
@@ -106,6 +115,36 @@
         verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(1),
                 eq(TelephonyManager.DATA_ACTIVITY_IN));
     }
+    @Test @SmallTest
+    public void testNotifyDataActivityWithSlot() throws Exception {
+        when(mFeatureFlags.notifyDataActivityChangedWithSlot()).thenReturn(true);
+        //mock data activity state
+        doReturn(TelephonyManager.DATA_ACTIVITY_NONE).when(mPhone).getDataActivityState();
+        doReturn(PHONE_ID).when(mPhone).getPhoneId();
+        mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
+        verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(1), eq(0),
+                eq(TelephonyManager.DATA_ACTIVITY_NONE));
+
+        doReturn(1/*subId*/).when(mPhone).getSubId();
+        doReturn(TelephonyManager.DATA_ACTIVITY_IN).when(mPhone).getDataActivityState();
+        mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
+        verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(1), eq(1),
+                eq(TelephonyManager.DATA_ACTIVITY_IN));
+
+        doReturn(SUB_ID).when(mPhone).getSubId();
+        doReturn(TelephonyManager.DATA_ACTIVITY_NONE).when(mPhone).getDataActivityState();
+        doReturn(2/*phoneId*/).when(mPhone).getPhoneId();
+        mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
+        verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(2), eq(0),
+                eq(TelephonyManager.DATA_ACTIVITY_NONE));
+
+        doReturn(1/*subId*/).when(mPhone).getSubId();
+        doReturn(TelephonyManager.DATA_ACTIVITY_INOUT).when(mPhone).getDataActivityState();
+        mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
+        verify(mTelephonyRegistryManager).notifyDataActivityChanged(
+                eq(2), eq(1), eq(TelephonyManager.DATA_ACTIVITY_INOUT));
+
+    }
 
     @Test @SmallTest
     public void testNotifySignalStrength() throws Exception {
@@ -342,4 +381,15 @@
         assertEquals(3, cellLocationCapture.getValue().asCellLocation().getCid());
         assertEquals(-1, cellLocationCapture.getValue().asCellLocation().getPsc());
     }
+
+    @Test
+    @SmallTest
+    public void testSimultaneousCellularCallingSubscriptionsChanged() {
+        ArraySet<Integer> subs = new ArraySet<>(2);
+        subs.add(0);
+        subs.add(1);
+        mDefaultPhoneNotifierUT.notifySimultaneousCellularCallingSubscriptionsChanged(subs);
+        verify(mTelephonyRegistryManager).notifySimultaneousCellularCallingSubscriptionsChanged(
+                eq(subs));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
index 28a37f7..c9e4c12 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
@@ -20,7 +20,9 @@
 import static android.hardware.radio.V1_0.DeviceStateType.POWER_SAVE_MODE;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
@@ -30,6 +32,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import static java.util.Arrays.asList;
 
@@ -40,22 +43,32 @@
 import android.hardware.radio.V1_5.IndicationFilter;
 import android.net.ConnectivityManager;
 import android.net.TetheringManager;
+import android.os.AsyncResult;
 import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.MediumTest;
+
+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.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -114,6 +127,7 @@
 
     private static final int STATE_OFF = 0;
     private static final int STATE_ON = 1;
+    private static final long TIMEOUT = 500;
 
     // The keys are the single IndicationFilter flags,
     // The values are the array of states, when one state turn on, the corresponding
@@ -135,6 +149,9 @@
     UiModeManager mUiModeManager;
 
     private DeviceStateMonitor mDSM;
+    private TestSatelliteController mSatelliteControllerUT;
+
+    @Mock private FeatureFlags mFeatureFlags;
 
     // Given a stateType, return the event type that can change the state
     private int state2Event(@StateType int stateType) {
@@ -162,11 +179,12 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
         mUiModeManager = mock(UiModeManager.class);
         mContextFixture.setSystemService(Context.UI_MODE_SERVICE, mUiModeManager);
         // We don't even need a mock executor, we just need to not throw.
         doReturn(null).when(mContextFixture.getTestDouble()).getMainExecutor();
-        mDSM = new DeviceStateMonitor(mPhone);
+        mDSM = new DeviceStateMonitor(mPhone, mFeatureFlags);
 
         // Initialize with ALL states off
         updateAllStatesToOff();
@@ -177,6 +195,7 @@
 
     @After
     public void tearDown() throws Exception {
+        mSatelliteControllerUT = null;
         mDSM = null;
         super.tearDown();
     }
@@ -453,4 +472,160 @@
         verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
                 eq(INDICATION_FILTERS_MINIMUM), nullable(Message.class));
     }
+
+    @Test
+    public void testRegisterForSignalStrengthReportDecisionWithFeatureEnabled() {
+        logd("testRegisterForSignalStrengthReportDecisionWithFeatureEnabled()");
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        mSatelliteControllerUT = new TestSatelliteController(Looper.myLooper(), mDSM);
+
+        updateState(STATE_TYPE_RADIO_OFF_OR_NOT_AVAILABLE, 0);
+        updateState(STATE_TYPE_SCREEN, STATE_OFF);
+        mSatelliteControllerUT.resetCount();
+        sEventDeviceStatusChanged.drainPermits();
+
+        updateState(STATE_TYPE_SCREEN, STATE_ON);
+        assertTrue(waitForEventDeviceStatusChanged());
+        assertEquals(0, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(1, mSatelliteControllerUT.getStopEventCount());
+        mSatelliteControllerUT.resetCount();
+
+        mSatelliteControllerUT.resetCount();
+        updateState(STATE_TYPE_SCREEN, STATE_OFF);
+        assertTrue(waitForEventDeviceStatusChanged());
+        assertEquals(0, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(1, mSatelliteControllerUT.getStopEventCount());
+        mSatelliteControllerUT.resetCount();
+
+        updateState(STATE_TYPE_RADIO_ON, 0);
+        assertTrue(waitForEventDeviceStatusChanged());
+        assertEquals(0, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(1, mSatelliteControllerUT.getStopEventCount());
+        mSatelliteControllerUT.resetCount();
+
+        updateState(STATE_TYPE_SCREEN, STATE_ON);
+        assertTrue(waitForEventDeviceStatusChanged());
+        assertEquals(1, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(0, mSatelliteControllerUT.getStopEventCount());
+        mSatelliteControllerUT.resetCount();
+
+        updateState(STATE_TYPE_RADIO_OFF_OR_NOT_AVAILABLE, 0);
+        assertTrue(waitForEventDeviceStatusChanged());
+        assertEquals(0, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(1, mSatelliteControllerUT.getStopEventCount());
+    }
+
+    @Test
+    public void testRegisterForSignalStrengthReportDecisionWithFeatureDisabled() {
+        logd("testRegisterForSignalStrengthReportDecisionWithFeatureDisabled()");
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+        mSatelliteControllerUT = new TestSatelliteController(Looper.myLooper(), mDSM);
+
+        updateState(STATE_TYPE_RADIO_OFF_OR_NOT_AVAILABLE, 0);
+        updateState(STATE_TYPE_SCREEN, STATE_OFF);
+        mSatelliteControllerUT.resetCount();
+        sEventDeviceStatusChanged.drainPermits();
+
+
+        /* Sending stop ntn signal strength as radio is off */
+        updateState(STATE_TYPE_SCREEN, STATE_ON);
+        assertFalse(waitForEventDeviceStatusChanged());
+        assertEquals(0, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(0, mSatelliteControllerUT.getStopEventCount());
+
+        updateState(STATE_TYPE_SCREEN, STATE_OFF);
+        assertFalse(waitForEventDeviceStatusChanged());
+        assertEquals(0, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(0, mSatelliteControllerUT.getStopEventCount());
+
+        updateState(STATE_TYPE_RADIO_ON, 0);
+        assertFalse(waitForEventDeviceStatusChanged());
+        assertEquals(0, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(0, mSatelliteControllerUT.getStopEventCount());
+
+        updateState(STATE_TYPE_SCREEN, STATE_ON);
+        assertFalse(waitForEventDeviceStatusChanged());
+        assertEquals(0, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(0, mSatelliteControllerUT.getStopEventCount());
+
+        updateState(STATE_TYPE_RADIO_OFF_OR_NOT_AVAILABLE, 0);
+        assertFalse(waitForEventDeviceStatusChanged());
+        assertEquals(0, mSatelliteControllerUT.getStartEventCount());
+        assertEquals(0, mSatelliteControllerUT.getStopEventCount());
+    }
+
+    private static Semaphore sEventDeviceStatusChanged = new Semaphore(0);
+    private boolean waitForEventDeviceStatusChanged() {
+        try {
+            if (!sEventDeviceStatusChanged.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                logd("Time out to receive EVENT_DEVICE_STATUS_CHANGED");
+                return false;
+            }
+        } catch (Exception ex) {
+            logd("waitForEventDeviceStatusChanged: ex=" + ex);
+            return false;
+        }
+        return true;
+    }
+
+    private static class TestSatelliteController extends Handler {
+        public static final int EVENT_DEVICE_STATUS_CHANGED = 35;
+        private final DeviceStateMonitor mDsm;
+        private int mStartEventCount;
+        private int mStopEventCount;
+
+        TestSatelliteController(Looper looper, DeviceStateMonitor dsm) {
+            super(looper);
+            mDsm = dsm;
+            mDsm.registerForSignalStrengthReportDecision(this, EVENT_DEVICE_STATUS_CHANGED, null);
+        }
+
+        /**
+         * Resets the count of occurred events.
+         */
+        public void resetCount() {
+            mStartEventCount = 0;
+            mStopEventCount = 0;
+        }
+
+        public int getStartEventCount() {
+            return mStartEventCount;
+        }
+
+        public int getStopEventCount() {
+            return mStopEventCount;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case EVENT_DEVICE_STATUS_CHANGED: {
+                    logd("EVENT_DEVICE_STATUS_CHANGED");
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    boolean shouldReport = (boolean) ar.result;
+                    if (shouldReport) {
+                        startSendingNtnSignalStrength();
+                    } else {
+                        stopSendingNtnSignalStrength();
+                    }
+                    try {
+                        sEventDeviceStatusChanged.release();
+                    } catch (Exception ex) {
+                        logd("waitForEventDeviceStatusChanged: ex=" + ex);
+                    }
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+
+        private void startSendingNtnSignalStrength() {
+            mStartEventCount++;
+        }
+
+        private void stopSendingNtnSignalStrength() {
+            mStopEventCount++;
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java
index abe2873..8eb2de6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java
@@ -42,11 +42,14 @@
 import android.testing.TestableLooper;
 import android.text.TextUtils;
 
+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.Mockito;
 
 import java.util.Collections;
 import java.util.concurrent.Executor;
@@ -60,6 +63,9 @@
     private static final String NUMERIC = MCC + MNC;
     private static final String NETWORK = "TestNet";
 
+    // Mocked classes
+    private FeatureFlags mFeatureFlags;
+
     private DisplayInfoController mDic;
     private ServiceStateTracker mSst;
     private ServiceStateTrackerTestHandler mSstHandler;
@@ -85,7 +91,7 @@
             ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener>
                     listenerArgumentCaptor = ArgumentCaptor.forClass(
                     CarrierConfigManager.CarrierConfigChangeListener.class);
-            mSst = new ServiceStateTracker(mPhone, mSimulatedCommands);
+            mSst = new ServiceStateTracker(mPhone, mSimulatedCommands, mFeatureFlags);
             verify(mCarrierConfigManager, atLeast(2)).registerCarrierConfigChangeListener(any(),
                     listenerArgumentCaptor.capture());
             mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(1);
@@ -99,9 +105,9 @@
         logd("DisplayInfoControllerTest setup!");
         super.setUp(getClass().getSimpleName());
 
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
         doReturn((Executor) Runnable::run).when(mContext).getMainExecutor();
         mBundle = mContextFixture.getCarrierConfigBundle();
-        mBundle.putBoolean(CarrierConfigManager.KEY_SHOW_ROAMING_INDICATOR_BOOL, true);
         mSstHandler = new ServiceStateTrackerTestHandler(getClass().getSimpleName());
         mSstHandler.start();
         waitUntilReady();
@@ -190,7 +196,7 @@
         assertFalse(ss.getRoaming()); // home
 
         doReturn(mSst).when(mPhone).getServiceStateTracker();
-        mDic = new DisplayInfoController(mPhone);
+        mDic = new DisplayInfoController(mPhone, mFeatureFlags);
         mDic.updateTelephonyDisplayInfo();
         TelephonyDisplayInfo tdi = mDic.getTelephonyDisplayInfo();
 
@@ -210,7 +216,7 @@
         assertFalse(ss.getRoaming()); // home
 
         doReturn(mSst).when(mPhone).getServiceStateTracker();
-        mDic = new DisplayInfoController(mPhone);
+        mDic = new DisplayInfoController(mPhone, mFeatureFlags);
         mDic.updateTelephonyDisplayInfo();
         TelephonyDisplayInfo tdi = mDic.getTelephonyDisplayInfo();
 
@@ -231,7 +237,7 @@
         assertTrue(ss1.getRoaming()); // roam
 
         doReturn(mSst).when(mPhone).getServiceStateTracker();
-        mDic = new DisplayInfoController(mPhone);
+        mDic = new DisplayInfoController(mPhone, mFeatureFlags);
         mDic.updateTelephonyDisplayInfo();
         TelephonyDisplayInfo tdi = mDic.getTelephonyDisplayInfo();
 
@@ -253,7 +259,7 @@
         assertFalse(ss.getRoaming()); // home
 
         doReturn(mSst).when(mPhone).getServiceStateTracker();
-        mDic = new DisplayInfoController(mPhone);
+        mDic = new DisplayInfoController(mPhone, mFeatureFlags);
         mDic.updateTelephonyDisplayInfo();
         TelephonyDisplayInfo tdi = mDic.getTelephonyDisplayInfo();
 
@@ -274,7 +280,7 @@
         assertTrue(ss1.getRoaming()); // roam
 
         doReturn(mSst).when(mPhone).getServiceStateTracker();
-        mDic = new DisplayInfoController(mPhone);
+        mDic = new DisplayInfoController(mPhone, mFeatureFlags);
         mDic.updateTelephonyDisplayInfo();
         TelephonyDisplayInfo tdi = mDic.getTelephonyDisplayInfo();
 
@@ -287,6 +293,7 @@
         mBundle.putStringArray(
                 CarrierConfigManager.KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, new String[] {NUMERIC});
         mBundle.putBoolean(CarrierConfigManager.KEY_SHOW_ROAMING_INDICATOR_BOOL, false);
+        doReturn(true).when(mFeatureFlags).hideRoamingIcon();
         sendCarrierConfigUpdate();
 
         changeRegState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
@@ -295,7 +302,7 @@
         assertTrue(ss1.getRoaming()); // roam
 
         doReturn(mSst).when(mPhone).getServiceStateTracker();
-        mDic = new DisplayInfoController(mPhone);
+        mDic = new DisplayInfoController(mPhone, mFeatureFlags);
         mDic.updateTelephonyDisplayInfo();
         TelephonyDisplayInfo tdi = mDic.getTelephonyDisplayInfo();
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index 3be8509..a13a92c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -127,7 +127,15 @@
                     + "," + Telephony.SimInfo.COLUMN_TP_MESSAGE_REF
                     + "  INTEGER DEFAULT -1,"
                     + Telephony.SimInfo.COLUMN_USER_HANDLE + " INTEGER DEFAULT "
-                    + UserHandle.USER_NULL
+                    + UserHandle.USER_NULL + ","
+                    + Telephony.SimInfo.COLUMN_SATELLITE_ENABLED + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER
+                    + " INTEGER DEFAULT 1, "
+                    + Telephony.SimInfo.COLUMN_IS_NTN + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_SERVICE_CAPABILITIES + " INTEGER DEFAULT 7,"
+                    + Telephony.SimInfo.COLUMN_TRANSFER_STATUS + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS + " TEXT"
                     + ");";
         }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FdnUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/FdnUtilsTest.java
index 2c48158..9da19bc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FdnUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FdnUtilsTest.java
@@ -163,4 +163,22 @@
 
         assertFalse(FdnUtils.isFDN("6502910000", "", fdnList));
     }
+
+    @Test
+    public void smscAddrInTwoStringsFormat_returnsTrue() {
+        ArrayList<AdnRecord> fdnList = initializeFdnList();
+        AdnRecord adnRecord = new AdnRecord(null, "1234560000");
+        fdnList.add(7, adnRecord);
+
+        assertTrue(FdnUtils.isFDN("\"1234560000\",124", "US", fdnList));
+    }
+
+    @Test
+    public void smscAddrInEmailIdFormat_returnsTrue() {
+        ArrayList<AdnRecord> fdnList = initializeFdnList();
+        AdnRecord adnRecord = new AdnRecord(null, "1234560000");
+        fdnList.add(8, adnRecord);
+
+        assertTrue(FdnUtils.isFDN("1234560000@ims.mnc.org", "US", fdnList));
+    }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java
index c658b16..bad32e9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java
@@ -47,11 +47,12 @@
 import android.telephony.gba.GbaService;
 import android.telephony.gba.IGbaService;
 import android.telephony.gba.UaSecurityProtocolIdentifier;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Log;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.metrics.RcsStats;
 
 import org.junit.After;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
index 5a90f4e..f76d8c5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
@@ -16,14 +16,12 @@
 
 package com.android.internal.telephony;
 
-import com.android.internal.telephony.GsmAlphabet;
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.uicc.IccUtils;
 
 import junit.framework.TestCase;
 
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
 public class GsmAlphabetTest extends TestCase {
 
     private static final String sGsmExtendedChars = "{|}\\[~]\f\u20ac";
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTest.java
index b29976f..5927807 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTest.java
@@ -16,11 +16,11 @@
 
 package com.android.internal.telephony;
 
-import android.test.suitebuilder.annotation.SmallTest;
-
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
index 2fdff9e..7de75ae 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
@@ -33,15 +33,14 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
 import android.telephony.emergency.EmergencyNumber;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
-import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -67,20 +66,16 @@
     // Mocked classes
     private GsmCdmaConnection mConnection;
     private Handler mHandler;
-    private DomainSelectionResolver mDomainSelectionResolver;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mConnection = mock(GsmCdmaConnection.class);
         mHandler = mock(Handler.class);
-        mDomainSelectionResolver = mock(DomainSelectionResolver.class);
-        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
-        DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
         mSimulatedCommands.setRadioPower(true, null);
         mPhone.mCi = this.mSimulatedCommands;
 
-        mCTUT = new GsmCdmaCallTracker(mPhone);
+        mCTUT = new GsmCdmaCallTracker(mPhone, mFeatureFlags);
         logd("GsmCdmaCallTracker initiated, waiting for Power on");
         /* Make sure radio state is power on before dial.
          * When radio state changed from off to on, CallTracker
@@ -91,7 +86,6 @@
     @After
     public void tearDown() throws Exception {
         mCTUT = null;
-        DomainSelectionResolver.setDomainSelectionResolver(null);
         super.tearDown();
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
index 8292e84..45f8c12 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
@@ -25,11 +25,12 @@
 import android.os.Looper;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
 
 import org.junit.After;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index 465880a..e493a18 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -21,7 +21,9 @@
 import static com.android.internal.telephony.Phone.EVENT_ICC_CHANGED;
 import static com.android.internal.telephony.Phone.EVENT_IMS_DEREGISTRATION_TRIGGERED;
 import static com.android.internal.telephony.Phone.EVENT_RADIO_AVAILABLE;
+import static com.android.internal.telephony.Phone.EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE;
 import static com.android.internal.telephony.Phone.EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE;
+import static com.android.internal.telephony.Phone.EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE;
 import static com.android.internal.telephony.Phone.EVENT_SRVCC_STATE_CHANGED;
 import static com.android.internal.telephony.Phone.EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED;
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
@@ -54,6 +56,7 @@
 
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.hardware.radio.modem.ImeiInfo;
 import android.os.AsyncResult;
 import android.os.Bundle;
@@ -72,23 +75,26 @@
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityCdma;
 import android.telephony.CellIdentityGsm;
+import android.telephony.CellularIdentifierDisclosure;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.RadioAccessFamily;
+import android.telephony.SecurityAlgorithmUpdate;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Log;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
@@ -113,6 +119,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
+import org.mockito.Mock;
 import org.mockito.Mockito;
 
 import java.util.ArrayList;
@@ -129,7 +136,6 @@
     private UiccSlot mUiccSlot;
     private CommandsInterface mMockCi;
     private AdnRecordCache adnRecordCache;
-    private DomainSelectionResolver mDomainSelectionResolver;
 
     //mPhoneUnderTest
     private GsmCdmaPhone mPhoneUT;
@@ -138,6 +144,7 @@
     // app is not currently debuggable. For now, we use the real device config and ensure that
     // we reset the cellular_security namespace property to its pre-test value after every test.
     private DeviceConfig.Properties mPreTestProperties;
+    @Mock private FeatureFlags mFeatureFlags;
 
     private static final int EVENT_EMERGENCY_CALLBACK_MODE_EXIT = 1;
     private static final int EVENT_EMERGENCY_CALL_TOGGLE = 2;
@@ -169,14 +176,14 @@
         mUiccPort = Mockito.mock(UiccPort.class);
         mMockCi = Mockito.mock(CommandsInterface.class);
         adnRecordCache = Mockito.mock(AdnRecordCache.class);
-        mDomainSelectionResolver = Mockito.mock(DomainSelectionResolver.class);
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
+
         doReturn(false).when(mSST).isDeviceShuttingDown();
         doReturn(true).when(mImsManager).isVolteEnabledByPlatform();
-        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
-        DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
 
         mPhoneUT = new GsmCdmaPhone(mContext, mSimulatedCommands, mNotifier, true, 0,
-            PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager);
+            PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager,
+                mFeatureFlags);
         mPhoneUT.setVoiceCallSessionStats(mVoiceCallSessionStats);
         ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mUiccController).registerForIccChanged(eq(mPhoneUT), integerArgumentCaptor.capture(),
@@ -191,7 +198,6 @@
     public void tearDown() throws Exception {
         mPhoneUT.removeCallbacksAndMessages(null);
         mPhoneUT = null;
-        DomainSelectionResolver.setDomainSelectionResolver(null);
         try {
             DeviceConfig.setProperties(mPreTestProperties);
         } catch (DeviceConfig.BadConfigException e) {
@@ -689,6 +695,32 @@
 
     @Test
     @SmallTest
+    public void testEmergencySmsModeWithTelephonyFeatureMapping() {
+        String emergencyNumber = "111";
+        int timeout = 200;
+        mContextFixture.getCarrierConfigBundle().putInt(
+                CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, timeout);
+        doReturn(true).when(mTelephonyManager).isEmergencyNumber(emergencyNumber);
+
+        // Feature flag enabled
+        // Device does not have FEATURE_TELEPHONY_CALLING
+        doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+        doReturn(false).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_CALLING));
+        mPhoneUT.notifySmsSent(emergencyNumber);
+        processAllMessages();
+        assertFalse(mPhoneUT.isInEmergencySmsMode());
+
+        // Device has FEATURE_TELEPHONY_CALLING
+        doReturn(true).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_CALLING));
+        mPhoneUT.notifySmsSent(emergencyNumber);
+        processAllMessages();
+        assertTrue(mPhoneUT.isInEmergencySmsMode());
+    }
+
+    @Test
+    @SmallTest
     public void testSendBurstDtmf() {
         //Should do nothing for GSM
         mPhoneUT.sendBurstDtmf("1234567890", 0, 0, null);
@@ -981,9 +1013,6 @@
         verify(mSimulatedCommandsVerifier).getBasebandVersion(nullable(Message.class));
         verify(mSimulatedCommandsVerifier).getDeviceIdentity(nullable(Message.class));
         verify(mSimulatedCommandsVerifier).getRadioCapability(nullable(Message.class));
-        // once as part of constructor, and once on radio available
-        verify(mSimulatedCommandsVerifier, times(2)).startLceService(anyInt(), anyBoolean(),
-                nullable(Message.class));
 
         // EVENT_RADIO_ON
         verify(mSimulatedCommandsVerifier).getVoiceRadioTechnology(nullable(Message.class));
@@ -1006,8 +1035,6 @@
         // EVENT_RADIO_AVAILABLE
         verify(mSimulatedCommandsVerifier, times(2)).getBasebandVersion(nullable(Message.class));
         verify(mSimulatedCommandsVerifier, times(2)).getDeviceIdentity(nullable(Message.class));
-        verify(mSimulatedCommandsVerifier, times(3)).startLceService(anyInt(), anyBoolean(),
-                nullable(Message.class));
 
         // EVENT_RADIO_ON
         verify(mSimulatedCommandsVerifier, times(2)).getVoiceRadioTechnology(
@@ -1040,7 +1067,8 @@
         };
 
         Phone phone = new GsmCdmaPhone(mContext, sc, mNotifier, true, 0,
-                PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager);
+                PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager,
+                mFeatureFlags);
         phone.setVoiceCallSessionStats(mVoiceCallSessionStats);
         ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mUiccController).registerForIccChanged(eq(phone), integerArgumentCaptor.capture(),
@@ -1482,6 +1510,176 @@
         assertEquals(captor.getValue().what, Phone.EVENT_GET_RADIO_CAPABILITY);
     }
 
+    private void setIsCarrierConfigForIdentifiedCarrier(
+            PersistableBundle carrierConfig, boolean isIdentified) {
+        carrierConfig.putBoolean(
+                CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL,
+                isIdentified);
+    }
+
+    @Test
+    public void testNrCapabilityChanged_firstRequest_incompleteCarrierConfig_changeNeeded() {
+        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+
+        mPhoneUT.mCi = mMockCi;
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+                new int[]{
+                    CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA});
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
+        processAllMessages();
+
+
+        verify(mMockCi, never()).isN1ModeEnabled(any());
+        verify(mMockCi, never()).setN1ModeEnabled(anyBoolean(), any());
+
+        setIsCarrierConfigForIdentifiedCarrier(bundle, true);
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
+        processAllMessages();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockCi, times(1)).isN1ModeEnabled(messageCaptor.capture());
+        AsyncResult.forMessage(messageCaptor.getValue(), Boolean.TRUE, null);
+        messageCaptor.getValue().sendToTarget();
+        processAllMessages();
+
+        verify(mMockCi, times(1)).setN1ModeEnabled(eq(false), messageCaptor.capture());
+    }
+
+    @Test
+    public void testNrCapabilityChanged_firstRequest_noChangeNeeded() {
+        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+
+        mPhoneUT.mCi = mMockCi;
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+                new int[]{
+                    CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA,
+                    CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA});
+        setIsCarrierConfigForIdentifiedCarrier(bundle, true);
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
+        processAllMessages();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockCi, times(1)).isN1ModeEnabled(messageCaptor.capture());
+        AsyncResult.forMessage(messageCaptor.getValue(), Boolean.TRUE, null);
+        messageCaptor.getValue().sendToTarget();
+        processAllMessages();
+
+        verify(mMockCi, never()).setN1ModeEnabled(anyBoolean(), any());
+    }
+
+    @Test
+    public void testNrCapabilityChanged_firstRequest_needsChange() {
+        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+
+        mPhoneUT.mCi = mMockCi;
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+                new int[]{
+                    CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA,
+                    CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA});
+        setIsCarrierConfigForIdentifiedCarrier(bundle, true);
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
+        processAllMessages();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockCi, times(1)).isN1ModeEnabled(messageCaptor.capture());
+        AsyncResult.forMessage(messageCaptor.getValue(), Boolean.FALSE, null);
+        messageCaptor.getValue().sendToTarget();
+        processAllMessages();
+
+        verify(mMockCi, times(1)).setN1ModeEnabled(eq(true), messageCaptor.capture());
+    }
+
+    @Test
+    public void testNrCapabilityChanged_CarrierConfigChanges() {
+        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+
+        // Initialize the inner cache and set the modem to N1 mode = enabled/true
+        testNrCapabilityChanged_firstRequest_needsChange();
+
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        // Remove SA support and send an additional carrier config change
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+                new int[]{CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA});
+        setIsCarrierConfigForIdentifiedCarrier(bundle, true);
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
+        processAllMessages();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockCi, times(1)).isN1ModeEnabled(any()); // not called again
+        verify(mMockCi, times(1)).setN1ModeEnabled(eq(false), messageCaptor.capture());
+    }
+
+    @Test
+    public void testNrCapabilityChanged_CarrierConfigChanges_ErrorResponse() {
+        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+
+        mPhoneUT.mCi = mMockCi;
+        for (int i = 0; i < 2; i++) {
+            PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+            bundle.putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+                    new int[]{
+                        CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA,
+                        CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA});
+            setIsCarrierConfigForIdentifiedCarrier(bundle, true);
+
+            mPhoneUT.sendMessage(mPhoneUT.obtainMessage(Phone.EVENT_CARRIER_CONFIG_CHANGED));
+            processAllMessages();
+
+            ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+            verify(mMockCi, times(i + 1)).isN1ModeEnabled(messageCaptor.capture());
+            AsyncResult.forMessage(messageCaptor.getValue(), null, new RuntimeException());
+            messageCaptor.getValue().sendToTarget();
+            processAllMessages();
+
+            verify(mMockCi, never()).setN1ModeEnabled(anyBoolean(), any());
+        }
+    }
+
+    @Test
+    public void testNrCapabilityChanged_firstRequest_ImsChanges() {
+        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+
+        mPhoneUT.mCi = mMockCi;
+        Message passthroughMessage = mTestHandler.obtainMessage(0xC0FFEE);
+
+        mPhoneUT.setN1ModeEnabled(false, passthroughMessage);
+        processAllMessages();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockCi, times(1)).isN1ModeEnabled(messageCaptor.capture());
+        assertEquals(messageCaptor.getValue().obj, passthroughMessage);
+        AsyncResult.forMessage(messageCaptor.getValue(), Boolean.TRUE, null);
+        messageCaptor.getValue().sendToTarget();
+        processAllMessages();
+
+        verify(mMockCi, times(1)).setN1ModeEnabled(eq(false), messageCaptor.capture());
+        assertEquals(messageCaptor.getValue().obj, passthroughMessage);
+        AsyncResult.forMessage(messageCaptor.getValue(), null, null);
+        messageCaptor.getValue().sendToTarget();
+        processAllMessages();
+
+        // Verify the return message was received
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mTestHandler, times(1)).sendMessageAtTime(messageArgumentCaptor.capture(),
+                anyLong());
+        assertEquals(messageArgumentCaptor.getValue(), passthroughMessage);
+
+        mPhoneUT.setN1ModeEnabled(true, null);
+        processAllMessages();
+
+        verify(mMockCi, times(1)).isN1ModeEnabled(any()); // not called again
+        verify(mMockCi, times(1)).setN1ModeEnabled(eq(true), messageCaptor.capture());
+    }
+
     private void setupForWpsCallTest() throws Exception {
         mSST.mSS = mServiceState;
         doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
@@ -2595,4 +2793,334 @@
         verify(mSimulatedCommandsVerifier).getNetworkSelectionMode(any(Message.class));
         verify(mSimulatedCommandsVerifier).setNetworkSelectionModeAutomatic(any(Message.class));
     }
+
+    /**
+     * Verify the ImeiMappingChange and EVENT_GET_DEVICE_IMEI_CHANGE_DONE are handled properly.
+     */
+    @Test
+    public void testChangeInPrimaryImei() {
+        // Initially assign the primaryImei and test it.
+        Message message = mPhoneUT.obtainMessage(Phone.EVENT_GET_DEVICE_IMEI_DONE);
+        ImeiInfo imeiInfo = new ImeiInfo();
+        imeiInfo.imei = FAKE_IMEI;
+        imeiInfo.svn = FAKE_IMEISV;
+        imeiInfo.type = ImeiInfo.ImeiType.PRIMARY;
+        AsyncResult.forMessage(message, imeiInfo, null);
+        mPhoneUT.handleMessage(message);
+        assertEquals(Phone.IMEI_TYPE_PRIMARY, mPhoneUT.getImeiType());
+        assertEquals(FAKE_IMEI, mPhoneUT.getImei());
+
+        // Now update the same one to secondary and check whether it is reflecting or not.
+        message = mPhoneUT.obtainMessage(Phone.EVENT_IMEI_MAPPING_CHANGED);
+        imeiInfo.imei = FAKE_IMEI;
+        imeiInfo.svn = FAKE_IMEISV;
+        imeiInfo.type = ImeiInfo.ImeiType.SECONDARY;
+        AsyncResult.forMessage(message, imeiInfo, null);
+        mPhoneUT.handleMessage(message);
+        assertEquals(Phone.IMEI_TYPE_SECONDARY, mPhoneUT.getImeiType());
+        assertEquals(FAKE_IMEI, mPhoneUT.getImei());
+    }
+
+    @Test
+    public void testCellularIdentifierDisclosureFlagOff() {
+        when(mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()).thenReturn(false);
+
+        GsmCdmaPhone phoneUT =
+                new GsmCdmaPhone(
+                        mContext,
+                        mSimulatedCommands,
+                        mNotifier,
+                        true,
+                        0,
+                        PhoneConstants.PHONE_TYPE_GSM,
+                        mTelephonyComponentFactory,
+                        (c, p) -> mImsManager,
+                        mFeatureFlags);
+        phoneUT.mCi = mMockCi;
+
+        verify(mMockCi, never())
+                .registerForCellularIdentifierDisclosures(
+                        any(Handler.class), anyInt(), any(Object.class));
+    }
+
+    @Test
+    public void testCellularIdentifierDisclosureFlagOn() {
+        when(mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()).thenReturn(true);
+
+        Phone phoneUT =
+                new GsmCdmaPhone(
+                        mContext,
+                        mMockCi,
+                        mNotifier,
+                        true,
+                        0,
+                        PhoneConstants.PHONE_TYPE_GSM,
+                        mTelephonyComponentFactory,
+                        (c, p) -> mImsManager,
+                        mFeatureFlags);
+
+        verify(mMockCi, times(1))
+                .registerForCellularIdentifierDisclosures(
+                        eq(phoneUT),
+                        eq(Phone.EVENT_CELL_IDENTIFIER_DISCLOSURE),
+                        nullable(Object.class));
+    }
+
+    @Test
+    public void testCellularIdentifierDisclosure_disclosureEventAddedToNotifier() {
+        int phoneId = 0;
+        int subId = 10;
+        when(mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()).thenReturn(true);
+        when(mSubscriptionManagerService.getSubId(phoneId)).thenReturn(subId);
+
+        Phone phoneUT =
+                new GsmCdmaPhone(
+                        mContext,
+                        mMockCi,
+                        mNotifier,
+                        true,
+                        phoneId,
+                        PhoneConstants.PHONE_TYPE_GSM,
+                        mTelephonyComponentFactory,
+                        (c, p) -> mImsManager,
+                        mFeatureFlags);
+
+        CellularIdentifierDisclosure disclosure =
+                new CellularIdentifierDisclosure(
+                        CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        false);
+        phoneUT.sendMessage(
+                mPhoneUT.obtainMessage(
+                        Phone.EVENT_CELL_IDENTIFIER_DISCLOSURE,
+                        new AsyncResult(null, disclosure, null)));
+        processAllMessages();
+
+        verify(mIdentifierDisclosureNotifier, times(1))
+                .addDisclosure(eq(mContext), eq(subId), eq(disclosure));
+    }
+
+    @Test
+    public void testCellularIdentifierDisclosure_disclosureEventNull() {
+        int phoneId = 4;
+        int subId = 6;
+        when(mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()).thenReturn(true);
+        when(mSubscriptionManagerService.getSubId(phoneId)).thenReturn(subId);
+        Phone phoneUT =
+                new GsmCdmaPhone(
+                        mContext,
+                        mMockCi,
+                        mNotifier,
+                        true,
+                        phoneId,
+                        PhoneConstants.PHONE_TYPE_GSM,
+                        mTelephonyComponentFactory,
+                        (c, p) -> mImsManager,
+                        mFeatureFlags);
+
+        phoneUT.sendMessage(
+                mPhoneUT.obtainMessage(
+                        Phone.EVENT_CELL_IDENTIFIER_DISCLOSURE, new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(mIdentifierDisclosureNotifier, never())
+                .addDisclosure(eq(mContext), eq(subId), any(CellularIdentifierDisclosure.class));
+    }
+
+    @Test
+    public void testCellularIdentifierDisclosure_noModemCallOnRadioAvailable_FlagOff() {
+        when(mFeatureFlags.enableIdentifierDisclosureTransparency()).thenReturn(false);
+        GsmCdmaPhone phoneUT = makeNewPhoneUT();
+        assertFalse(phoneUT.isIdentifierDisclosureTransparencySupported());
+
+        sendRadioAvailableToPhone(phoneUT);
+
+        verify(mMockCi, never()).setCellularIdentifierTransparencyEnabled(anyBoolean(),
+                any(Message.class));
+        assertFalse(phoneUT.isIdentifierDisclosureTransparencySupported());
+    }
+
+    @Test
+    public void testCellularIdentifierDisclosure_unsupportedByModemOnRadioAvailable() {
+        when(mFeatureFlags.enableIdentifierDisclosureTransparency()).thenReturn(true);
+        GsmCdmaPhone phoneUT = makeNewPhoneUT();
+        assertFalse(phoneUT.isIdentifierDisclosureTransparencySupported());
+
+        // The following block emulates incoming messages from the modem in the case that
+        // the modem does not support the new HAL APIs. We expect the phone instance to attempt
+        // to set cipher-identifier-transparency-enabled state when the radio becomes available.
+        sendRadioAvailableToPhone(phoneUT);
+        verify(mMockCi, times(1)).setCellularIdentifierTransparencyEnabled(anyBoolean(),
+                any(Message.class));
+        sendRequestNotSupportedToPhone(phoneUT, EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE);
+
+        assertFalse(phoneUT.isIdentifierDisclosureTransparencySupported());
+    }
+
+    @Test
+    public void testCellularIdentifierDisclosure_supportedByModem() {
+        when(mFeatureFlags.enableIdentifierDisclosureTransparency()).thenReturn(true);
+        GsmCdmaPhone phoneUT = makeNewPhoneUT();
+        assertFalse(phoneUT.isIdentifierDisclosureTransparencySupported());
+
+        // The following block emulates incoming messages from the modem in the case that
+        // the modem supports the new HAL APIs. We expect the phone instance to attempt
+        // to set cipher-identifier-transparency-enabled state when the radio becomes available.
+        sendRadioAvailableToPhone(phoneUT);
+        verify(mMockCi, times(1)).setCellularIdentifierTransparencyEnabled(anyBoolean(),
+                any(Message.class));
+        sendRequestSuccessToPhone(phoneUT, EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE);
+
+        assertTrue(phoneUT.isIdentifierDisclosureTransparencySupported());
+    }
+
+    @Test
+    public void testSecurityAlgorithmUpdateFlagOff() {
+        when(mFeatureFlags.enableModemCipherTransparency()).thenReturn(false);
+
+        makeNewPhoneUT();
+
+        verify(mMockCi, never()).registerForSecurityAlgorithmUpdates(any(), anyInt(), any());
+    }
+
+    @Test
+    public void testSecurityAlgorithmUpdateFlagOn() {
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(true);
+
+        Phone phoneUT = makeNewPhoneUT();
+
+        verify(mMockCi, times(1))
+                .registerForSecurityAlgorithmUpdates(
+                        eq(phoneUT),
+                        eq(Phone.EVENT_SECURITY_ALGORITHM_UPDATE),
+                        any());
+    }
+
+    @Test
+    public void testSecurityAlgorithm_updateAddedToNotifier() {
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(true);
+        Phone phoneUT = makeNewPhoneUT();
+        SecurityAlgorithmUpdate update =
+                new SecurityAlgorithmUpdate(
+                        SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_3G,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA1,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+                        true);
+
+        phoneUT.sendMessage(
+                mPhoneUT.obtainMessage(
+                        Phone.EVENT_SECURITY_ALGORITHM_UPDATE,
+                        new AsyncResult(null, update, null)));
+        processAllMessages();
+
+        verify(mNullCipherNotifier, times(1)).onSecurityAlgorithmUpdate(eq(0), eq(update));
+    }
+
+    @Test
+    public void testNullCipherNotification_noModemCallOnRadioAvailable_FlagOff() {
+        when(mFeatureFlags.enableModemCipherTransparency()).thenReturn(false);
+        GsmCdmaPhone phoneUT = makeNewPhoneUT();
+        assertFalse(phoneUT.isNullCipherNotificationSupported());
+
+        sendRadioAvailableToPhone(phoneUT);
+
+        verify(mMockCi, never()).setSecurityAlgorithmsUpdatedEnabled(anyBoolean(),
+                any(Message.class));
+        assertFalse(phoneUT.isNullCipherNotificationSupported());
+    }
+
+    @Test
+    public void testNullCipherNotification_unsupportedByModemOnRadioAvailable() {
+        when(mFeatureFlags.enableModemCipherTransparency()).thenReturn(true);
+        GsmCdmaPhone phoneUT = makeNewPhoneUT();
+        assertFalse(phoneUT.isNullCipherNotificationSupported());
+
+        sendRadioAvailableToPhone(phoneUT);
+        verify(mMockCi, times(1)).setSecurityAlgorithmsUpdatedEnabled(anyBoolean(),
+                any(Message.class));
+        sendRequestNotSupportedToPhone(phoneUT, EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE);
+
+        assertFalse(phoneUT.isNullCipherNotificationSupported());
+    }
+
+    @Test
+    public void testNullCipherNotification_supportedByModem() {
+        when(mFeatureFlags.enableModemCipherTransparency()).thenReturn(true);
+        GsmCdmaPhone phoneUT = makeNewPhoneUT();
+        assertFalse(phoneUT.isNullCipherNotificationSupported());
+
+        sendRadioAvailableToPhone(phoneUT);
+        verify(mMockCi, times(1)).setSecurityAlgorithmsUpdatedEnabled(anyBoolean(),
+                any(Message.class));
+        sendRequestSuccessToPhone(phoneUT, EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE);
+
+        assertTrue(phoneUT.isNullCipherNotificationSupported());
+    }
+
+    @Test
+    public void testNullCipherNotification_preferenceEnabled() {
+        when(mFeatureFlags.enableModemCipherTransparency()).thenReturn(true);
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(true);
+        GsmCdmaPhone phoneUT = makeNewPhoneUT();
+
+        setNullCipherNotificationPreferenceEnabled(true);
+        phoneUT.handleNullCipherNotificationPreferenceChanged();
+
+        verify(mNullCipherNotifier, times(1)).enable();
+        verify(mMockCi, times(1)).setSecurityAlgorithmsUpdatedEnabled(eq(true),
+                any(Message.class));
+    }
+
+    @Test
+    public void testNullCipherNotification_preferenceDisabled() {
+        when(mFeatureFlags.enableModemCipherTransparency()).thenReturn(true);
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(true);
+        GsmCdmaPhone phoneUT = makeNewPhoneUT();
+
+        setNullCipherNotificationPreferenceEnabled(false);
+        phoneUT.handleNullCipherNotificationPreferenceChanged();
+
+        verify(mNullCipherNotifier, times(1)).disable();
+        verify(mMockCi, times(1)).setSecurityAlgorithmsUpdatedEnabled(eq(false),
+                any(Message.class));
+    }
+
+    private void sendRadioAvailableToPhone(GsmCdmaPhone phone) {
+        phone.sendMessage(phone.obtainMessage(EVENT_RADIO_AVAILABLE,
+                new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_GSM}, null)));
+        processAllMessages();
+    }
+
+    private void sendRequestNotSupportedToPhone(GsmCdmaPhone phone, int eventId) {
+        phone.sendMessage(phone.obtainMessage(eventId, new AsyncResult(null, null,
+                new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED))));
+        processAllMessages();
+    }
+
+    private void sendRequestSuccessToPhone(GsmCdmaPhone phone, int eventId) {
+        phone.sendMessage(phone.obtainMessage(eventId, new AsyncResult(null, null, null)));
+        processAllMessages();
+    }
+
+    private void setNullCipherNotificationPreferenceEnabled(boolean enabled) {
+        SharedPreferences sharedPreferences =
+                PreferenceManager.getDefaultSharedPreferences(mContext);
+        SharedPreferences.Editor editor = sharedPreferences.edit();
+        editor.putBoolean(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED, enabled);
+        editor.apply();
+    }
+
+    private GsmCdmaPhone makeNewPhoneUT() {
+        return new GsmCdmaPhone(
+                mContext,
+                mMockCi,
+                mNotifier,
+                true,
+                0,
+                PhoneConstants.PHONE_TYPE_GSM,
+                mTelephonyComponentFactory,
+                (c, p) -> mImsManager,
+                mFeatureFlags);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
index 3d4ef03..776715c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
@@ -18,7 +18,8 @@
 
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.gsm.SmsMessage;
 import com.android.internal.util.HexDump;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/IccLogicalChannelRequestTest.java b/tests/telephonytests/src/com/android/internal/telephony/IccLogicalChannelRequestTest.java
index e5e6fe1..aeaa096 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/IccLogicalChannelRequestTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/IccLogicalChannelRequestTest.java
@@ -22,7 +22,8 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.telephony.SubscriptionManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import junit.framework.TestCase;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/IccServiceTableTest.java b/tests/telephonytests/src/com/android/internal/telephony/IccServiceTableTest.java
index f715266..05b5cb5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/IccServiceTableTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/IccServiceTableTest.java
@@ -17,7 +17,8 @@
 package com.android.internal.telephony;
 
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.uicc.IccServiceTable;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
index fe1404b..f64155c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
@@ -38,10 +38,11 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SmsMessage;
 import android.telephony.ims.stub.ImsSmsImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.ims.FeatureConnector;
 import com.android.ims.ImsManager;
 import com.android.internal.util.HexDump;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ImsiEncryptionInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/ImsiEncryptionInfoTest.java
index 0fcb384..8fba956 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ImsiEncryptionInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ImsiEncryptionInfoTest.java
@@ -20,9 +20,10 @@
 import android.os.Parcel;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
index d251cf5..08a1b9a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
@@ -22,7 +22,8 @@
 
 import android.database.Cursor;
 import android.database.MatrixCursor;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.HexDump;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java
index ee75826..663fd3e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java
@@ -17,7 +17,8 @@
 package com.android.internal.telephony;
 
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
index 03b1cfd..6e50d88 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
@@ -22,8 +22,10 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Intent;
 import android.os.AsyncResult;
@@ -34,15 +36,18 @@
 import android.telephony.CellInfoGsm;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -65,13 +70,18 @@
     private LocaleTracker mLocaleTracker;
 
     private CellInfoGsm mCellInfo;
+    @Mock TelephonyCountryDetector mCountryDetector;
 
     @Before
     public void setUp() throws Exception {
-        logd("LocaleTrackerTest +Setup!");
         super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
 
-        mLocaleTracker = new LocaleTracker(mPhone, mNitzStateMachine, Looper.myLooper());
+        mLocaleTracker =
+                new LocaleTracker(mPhone, mNitzStateMachine, Looper.myLooper(), mFeatureFlags);
+        replaceInstance(TelephonyCountryDetector.class, "sInstance", null,
+                mCountryDetector);
 
         // This is a workaround to bypass setting system properties, which causes access violation.
         doReturn(-1).when(mPhone).getPhoneId();
@@ -334,4 +344,31 @@
         sendServiceState(ServiceState.STATE_IN_SERVICE);
         assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
     }
+
+    @Test
+    public void testNotifyCountryCodeChangedToTelephonyCountryDetector_featureFlagEnabled() {
+        testNotifyCountryCodeChangedToTelephonyCountryDetector(true);
+    }
+
+    @Test
+    public void testNotifyCountryCodeChangedToTelephonyCountryDetector_featureFlagDisabled() {
+        testNotifyCountryCodeChangedToTelephonyCountryDetector(false);
+    }
+
+    private void testNotifyCountryCodeChangedToTelephonyCountryDetector(
+            boolean oemEnabledSatelliteFlag) {
+        reset(mCountryDetector);
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(oemEnabledSatelliteFlag);
+        doReturn(true).when(mPhone).isRadioOn();
+        sendServiceState(ServiceState.STATE_IN_SERVICE);
+        mLocaleTracker.updateOperatorNumeric(US_MCC + FAKE_MNC);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getLastKnownCountryIso());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
+        assertFalse(mLocaleTracker.isTracking());
+
+        int notifiedCount = oemEnabledSatelliteFlag ? 1 : 0;
+        verify(mCountryDetector, times(notifiedCount))
+                .onNetworkCountryCodeChanged(mPhone, US_COUNTRY_CODE);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java b/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
index fcffa96..658935f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
@@ -19,9 +19,9 @@
 import static org.junit.Assert.assertEquals;
 
 import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.MccTable.MccMnc;
 import com.android.internal.telephony.util.LocaleUtils;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MissedIncomingCallSmsFilterTest.java b/tests/telephonytests/src/com/android/internal/telephony/MissedIncomingCallSmsFilterTest.java
index 510103c..d6dd573 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MissedIncomingCallSmsFilterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MissedIncomingCallSmsFilterTest.java
@@ -27,7 +27,8 @@
 import android.os.PersistableBundle;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.uicc.IccUtils;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ModemInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/ModemInfoTest.java
index 6e646d5..dc57a23 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ModemInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ModemInfoTest.java
@@ -21,7 +21,8 @@
 
 import android.os.Parcel;
 import android.telephony.ModemInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
index a41dbe1..4c68e26 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -52,11 +52,11 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.data.DataSettingsManager;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -198,7 +198,7 @@
             }
             return subscriptionInfoList;
         }).when(mSubscriptionManagerService).getActiveSubscriptionInfoList(
-                anyString(), nullable(String.class));
+                anyString(), nullable(String.class), anyBoolean());
 
         doAnswer(invocation -> {
             final boolean visibleOnly = (boolean) invocation.getArguments()[0];
@@ -247,6 +247,10 @@
             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }).when(mPhoneMock2).getSubId();
 
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
         // Capture listener to emulate the carrier config change notification used later
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
@@ -885,6 +889,13 @@
         doReturn(true).when(mPhoneMock2).isUserDataEnabled();
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
         processAllMessages();
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(eq(1));
+        PersistableBundle bundle2 = new PersistableBundle();
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(eq(2));
+
         sendCarrierConfigChanged(0, 1);
         // Notify carrier config change on phone1 without specifying subId.
         sendCarrierConfigChanged(1, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -893,13 +904,9 @@
         verify(mDataSettingsManagerMock2, never()).setDataEnabled(
                 TelephonyManager.DATA_ENABLED_REASON_USER, false, PHONE_PACKAGE);
 
-        // Still notify carrier config without specifying subId2, but this time subController
-        // and CarrierConfigManager have subId 2 active and ready.
-        doReturn(2).when(mSubscriptionManagerService).getSubId(1);
-        CarrierConfigManager cm = (CarrierConfigManager) mContext.getSystemService(
-                mContext.CARRIER_CONFIG_SERVICE);
-        doReturn(new PersistableBundle()).when(cm).getConfigForSubId(2);
-        sendCarrierConfigChanged(1, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        logd("Sending the correct phone id and sub id");
+        bundle2.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        sendCarrierConfigChanged(1, 2);
         processAllMessages();
         // This time user data should be disabled on phone1.
         verify(mDataSettingsManagerMock2).setDataEnabled(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NeighboringCellInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/NeighboringCellInfoTest.java
index f49fffd..e0e0aa1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NeighboringCellInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NeighboringCellInfoTest.java
@@ -15,15 +15,16 @@
  */
 package com.android.internal.telephony;
 
-import android.os.Parcel;
-import android.test.AndroidTestCase;
-import android.telephony.NeighboringCellInfo;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import static android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_EDGE;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_GPRS;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_UMTS;
+import static android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN;
+
+import android.os.Parcel;
+import android.telephony.NeighboringCellInfo;
+import android.test.AndroidTestCase;
+
+import androidx.test.filters.SmallTest;
 
 public class NeighboringCellInfoTest extends AndroidTestCase {
     @SmallTest
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkScanRequestTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkScanRequestTest.java
index 4538dea..02780bd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkScanRequestTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkScanRequestTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 
 import android.os.Parcel;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
@@ -30,6 +31,7 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /** Unit tests for {@link NetworkScanRequest}. */
 public class NetworkScanRequestTest {
@@ -37,6 +39,137 @@
     @Test
     @SmallTest
     public void testParcel() {
+        NetworkScanRequest nsq = createNetworkScanRequest();
+
+        Parcel p = Parcel.obtain();
+        nsq.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        NetworkScanRequest newNsq = NetworkScanRequest.CREATOR.createFromParcel(p);
+        assertEquals(nsq, newNsq);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_identity_allFieldsNonNull() {
+        NetworkScanRequest nsq = createNetworkScanRequest();
+
+        assertEquals(nsq, nsq);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_identify_nullRadioAccessSpecifiers() {
+        NetworkScanRequest nsq = createNetworkScanRequest(null, List.of("310480"));
+
+        assertEquals(nsq, nsq);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_identify_emptyRadioAccessSpecifiers() {
+        NetworkScanRequest nsq = createNetworkScanRequest(new RadioAccessSpecifier[]{},
+                List.of("310480"));
+
+        assertEquals(nsq, nsq);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_identify_nullPlmns() {
+        NetworkScanRequest nsq = createNetworkScanRequest(new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null)}, null);
+
+        assertEquals(nsq, nsq);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_identify_emptyPlmns() {
+        NetworkScanRequest nsq = createNetworkScanRequest(new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null)}, List.of());
+
+        assertEquals(nsq, nsq);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_identify_nullRasAndPlmns() {
+        NetworkScanRequest nsq = createNetworkScanRequest(null, null);
+
+        assertEquals(nsq, nsq);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_sameValues_allFieldsNonNull() {
+        NetworkScanRequest nsq1 = createNetworkScanRequest();
+        NetworkScanRequest nsq2 = createNetworkScanRequest();
+
+        assertEquals(nsq1, nsq2);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_sameValues_nullRadioAccessSpecifiers() {
+        NetworkScanRequest nsq1 = createNetworkScanRequest(null, List.of("310480"));
+        NetworkScanRequest nsq2 = createNetworkScanRequest(null, List.of("310480"));
+
+        assertEquals(nsq1, nsq2);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_sameValues_emptyRadioAccessSpecifiers() {
+        NetworkScanRequest nsq1 = createNetworkScanRequest(new RadioAccessSpecifier[]{},
+                List.of("310480"));
+        NetworkScanRequest nsq2 = createNetworkScanRequest(new RadioAccessSpecifier[]{},
+                List.of("310480"));
+
+        assertEquals(nsq1, nsq2);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_sameValues_nullPlmns() {
+        NetworkScanRequest nsq1 = createNetworkScanRequest(new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null)}, null);
+        NetworkScanRequest nsq2 = createNetworkScanRequest(new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null)}, null);
+
+        assertEquals(nsq1, nsq2);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_sameValues_emptyPlmns() {
+        NetworkScanRequest nsq1 = createNetworkScanRequest(new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null)}, List.of());
+        NetworkScanRequest nsq2 = createNetworkScanRequest(new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null)}, List.of());
+
+        assertEquals(nsq1, nsq2);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_sameValues_nullRasAndPlmns() {
+        NetworkScanRequest nsq1 = createNetworkScanRequest(null, null);
+        NetworkScanRequest nsq2 = createNetworkScanRequest(null, null);
+
+        assertEquals(nsq1, nsq2);
+    }
+
+    @Test
+    @SmallTest
+    public void testEquals_plmnsInDifferentOrder_shouldNotEqual() {
+        NetworkScanRequest nsq1 = createNetworkScanRequest(null, List.of("123456", "987654"));
+        NetworkScanRequest nsq2 = createNetworkScanRequest(null, List.of("987654", "123456"));
+
+        assertNotEquals(nsq1, nsq2);
+    }
+
+    private NetworkScanRequest createNetworkScanRequest() {
         int ranGsm = AccessNetworkType.GERAN;
         int[] gsmBands = {GeranBand.BAND_T380, GeranBand.BAND_T410};
         int[] gsmChannels = {1, 2, 3, 4};
@@ -46,22 +179,30 @@
         int[] lteChannels = {5, 6, 7, 8};
         RadioAccessSpecifier lte = new RadioAccessSpecifier(ranLte, lteBands, lteChannels);
         RadioAccessSpecifier[] ras = {gsm, lte};
+
         int searchPeriodicity = 70;
         int maxSearchTime = 200;
         boolean incrementalResults = true;
         int incrementalResultsPeriodicity = 7;
+
         ArrayList<String> mccmncs = new ArrayList<String>();
         mccmncs.add("310480");
         mccmncs.add("21002");
-        NetworkScanRequest nsq = new NetworkScanRequest(NetworkScanRequest.SCAN_TYPE_ONE_SHOT, ras,
-                  searchPeriodicity, maxSearchTime, incrementalResults,
-                  incrementalResultsPeriodicity, mccmncs);
 
-        Parcel p = Parcel.obtain();
-        nsq.writeToParcel(p, 0);
-        p.setDataPosition(0);
+        return new NetworkScanRequest(NetworkScanRequest.SCAN_TYPE_ONE_SHOT, ras,
+                searchPeriodicity, maxSearchTime, incrementalResults,
+                incrementalResultsPeriodicity, mccmncs);
+    }
 
-        NetworkScanRequest newNsq = NetworkScanRequest.CREATOR.createFromParcel(p);
-        assertEquals(nsq, newNsq);
+    private NetworkScanRequest createNetworkScanRequest(
+            RadioAccessSpecifier[] radioAccessSpecifiers, List<String> plmns) {
+        int searchPeriodicity = 70;
+        int maxSearchTime = 200;
+        boolean incrementalResults = true;
+        int incrementalResultsPeriodicity = 7;
+
+        return new NetworkScanRequest(NetworkScanRequest.SCAN_TYPE_ONE_SHOT, radioAccessSpecifiers,
+                searchPeriodicity, maxSearchTime, incrementalResults,
+                incrementalResultsPeriodicity, plmns != null ? new ArrayList<>(plmns) : null);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkScanRequestTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkScanRequestTrackerTest.java
new file mode 100644
index 0000000..f0ffe34
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkScanRequestTrackerTest.java
@@ -0,0 +1,924 @@
+/*
+ * Copyright (C) 2023 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.nullable;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.pm.PackageManager;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.UserHandle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CellInfoLte;
+import android.telephony.NetworkScan;
+import android.telephony.NetworkScanRequest;
+import android.telephony.RadioAccessSpecifier;
+import android.telephony.TelephonyScanManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.internal.telephony.NetworkScanRequestTracker.NetworkScanRequestInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit test for NetworkScanRequestTracker.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NetworkScanRequestTrackerTest extends TelephonyTest {
+    private static final String TAG = "NetworkScanRequestTrackerTest";
+
+    private static final String CLIENT_PKG = "com.android.testapp";
+    private static final int CLIENT_UID = 123456;
+
+    // Keep the same as in NetworkScanRequestTracker.
+    // This is the only internal implementation that the UT has to depend on
+    // in order to fully emulate NetworkScanResult in various cases
+    private static final int EVENT_RECEIVE_NETWORK_SCAN_RESULT = 3;
+
+    // Mocks
+    private CommandsInterface mMockCi;
+
+    private NetworkScanRequestTracker mNetworkScanRequestTracker;
+    private HandlerThread mTestHandlerThread;
+    private Handler mHandler;
+    private int mScanId;
+    private List<Message> mMessages = new ArrayList<>();
+    // Latch to make sure all messages are received before verifying.
+    // Default count is 1 but can override to match expected msg number
+    private CountDownLatch mMessageLatch = new CountDownLatch(1);
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        mMockCi = Mockito.mock(CommandsInterface.class);
+        mPhone.mCi = mMockCi;
+
+        mTestHandlerThread = new HandlerThread(TAG);
+        mTestHandlerThread.start();
+        mHandler = new Handler(mTestHandlerThread.getLooper()) {
+            @Override
+            public void handleMessage(Message message) {
+                Log.d(TAG, "Received msg: " + message);
+                mMessages.add(Message.obtain(message));
+                mMessageLatch.countDown();
+            }
+        };
+
+        mNetworkScanRequestTracker = new NetworkScanRequestTracker();
+        mScanId = TelephonyScanManager.INVALID_SCAN_ID;
+        setHasLocationPermissions(false);
+        assertThat(mHandler).isNotNull();
+        processAllMessages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        stopNetworkScanIfNeeded(mScanId);
+        mTestHandlerThread.quit();
+        mMessages.clear();
+        super.tearDown();
+    }
+
+    @Test
+    public void testStartNetworkScan_nullRequest_shouldNeverScan() throws Exception {
+        NetworkScanRequest request = null;
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when the request is null!"))
+                .startNetworkScan(any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithNullSpecifier_shouldNeverScan() throws Exception {
+        RadioAccessSpecifier[] specifiers = null;
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers,
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when no RadioAccessSpecifier is provided!"))
+                .startNetworkScan(any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithEmptySpecifier_shouldNeverScan() throws Exception {
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{};
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers,
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when empty RadioAccessSpecifier is "
+                        + "provided!")).startNetworkScan(
+                any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithTooManySpecifiers_shouldNeverScan()
+            throws Exception {
+        // More than NetworkScanRequest.MAX_RADIO_ACCESS_NETWORKS (8)
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        new int[]{AccessNetworkConstants.GeranBand.BAND_T380}, null),
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        new int[]{AccessNetworkConstants.GeranBand.BAND_T410}, null),
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        new int[]{AccessNetworkConstants.GeranBand.BAND_450}, null),
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        new int[]{AccessNetworkConstants.GeranBand.BAND_480}, null),
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        new int[]{AccessNetworkConstants.GeranBand.BAND_710}, null),
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        new int[]{AccessNetworkConstants.GeranBand.BAND_750}, null),
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        new int[]{AccessNetworkConstants.GeranBand.BAND_T810}, null),
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        new int[]{AccessNetworkConstants.GeranBand.BAND_850}, null),
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        new int[]{AccessNetworkConstants.GeranBand.BAND_P900}, null),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when request with too many "
+                        + "RadioAccessSpecifiers!")).startNetworkScan(
+                any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithTooManyBands_shouldNeverScan() throws Exception {
+        // More than NetworkScanRequest.MAX_BANDS (8)
+        int[] bands = new int[]{
+                AccessNetworkConstants.GeranBand.BAND_T380,
+                AccessNetworkConstants.GeranBand.BAND_T410,
+                AccessNetworkConstants.GeranBand.BAND_450,
+                AccessNetworkConstants.GeranBand.BAND_480,
+                AccessNetworkConstants.GeranBand.BAND_710,
+                AccessNetworkConstants.GeranBand.BAND_750,
+                AccessNetworkConstants.GeranBand.BAND_T810,
+                AccessNetworkConstants.GeranBand.BAND_850,
+                AccessNetworkConstants.GeranBand.BAND_P900,
+        };
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        bands, null),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when request with too many bands!"))
+                .startNetworkScan(any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithTooManyChannels_shouldNeverScan() throws Exception {
+        // More than NetworkScanRequest.MAX_CHANNELS (32)
+        int[] channels =
+                new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+                        22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
+                };
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        null, channels),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when request with too many channels!"))
+                .startNetworkScan(any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithUnsupportedRan_shouldNeverScan() throws Exception {
+        int[] unsupportedRans = new int[]{
+                AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+                AccessNetworkConstants.AccessNetworkType.CDMA2000,
+                AccessNetworkConstants.AccessNetworkType.IWLAN,
+        };
+        for (int ran : unsupportedRans) {
+            RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                    new RadioAccessSpecifier(ran, null, null),
+            };
+            NetworkScanRequest request = new NetworkScanRequest(
+                    NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                    specifiers, /* specifiers */
+                    5 /* searchPeriodicity */,
+                    60 /* maxSearchTime in seconds */,
+                    true /* incrementalResults */,
+                    5 /* incrementalResultsPeriodicity */,
+                    null /* PLMNs */);
+            Messenger messenger = new Messenger(mHandler);
+
+            int scanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                    mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+            processAllMessages();
+
+            verify(mPhone, never().description(
+                    "Phone should never start network scan with unsupported RAN "
+                            + ran + "!")).startNetworkScan(any(), any());
+
+            // Nothing is needed to clean up on success.
+            // This is just for failure cases when startNetworkScan was issued.
+            stopNetworkScanIfNeeded(scanId);
+        }
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithTooSmallPeriodicity_shouldNeverScan()
+            throws Exception {
+        int searchPeriodicity = NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC - 1;
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        null, null),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                searchPeriodicity /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when request with too small periodicity!"))
+                .startNetworkScan(any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithTooLargePeriodicity_shouldNeverScan()
+            throws Exception {
+        int searchPeriodicity = NetworkScanRequest.MAX_SEARCH_MAX_SEC + 1;
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        null, null),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                searchPeriodicity /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan with too large periodicity!"))
+                .startNetworkScan(any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithTooSmallIncPeriodicity_shouldNeverScan()
+            throws Exception {
+        int incPeriodicity = NetworkScanRequest.MIN_INCREMENTAL_PERIODICITY_SEC - 1;
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        null, null),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                incPeriodicity /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan with too small incremental results "
+                        + "periodicity!")).startNetworkScan(
+                any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestWithTooLargeIncPeriodicity_shouldNeverScan()
+            throws Exception {
+        int incPeriodicity = NetworkScanRequest.MAX_INCREMENTAL_PERIODICITY_SEC + 1;
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        null, null),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                incPeriodicity /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan with too large incremental periodicity!"))
+                .startNetworkScan(any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestPeriodBiggerThanMax_shouldNeverScan() throws Exception {
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        null, null),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                61 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when periodicity is larger than max search"
+                        + " time!")).startNetworkScan(
+                any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestIncPeriodBiggerThanMax_shouldNeverScan()
+            throws Exception {
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        null, null),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                61 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when incremental results periodicity is "
+                        + "larger than max search time!")).startNetworkScan(
+                any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_requestTooManyPlmns_shouldNeverScan() throws Exception {
+        // More than NetworkScanRequest.MAX_MCC_MNC_LIST_SIZE (20) PLMNs
+        List<String> plmns = List.of("11110", "11111", "11112", "11113", "11114", "11115", "11116",
+                "11117", "11118", "11119", "11120", "11121", "11122", "11123", "11124", "11125",
+                "11126", "11127", "11128", "11129", "11130", "11131");
+
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN,
+                        null, null),
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers, /* specifiers */
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                new ArrayList<>(plmns) /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+
+        mScanId = mNetworkScanRequestTracker.startNetworkScan(true, request, messenger,
+                mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        verify(mPhone, never().description(
+                "Phone should never start network scan when request with too many PLMNs!"))
+                .startNetworkScan(any(), any());
+        verifyMessage(TelephonyScanManager.CALLBACK_SCAN_ERROR, NetworkScan.ERROR_INVALID_SCAN,
+                mScanId, null);
+    }
+
+    @Test
+    public void testStartNetworkScan_succeed_returnValidScanId() throws Exception {
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+
+        verify(mPhone).startNetworkScan(any(), any());
+        assertThat(mScanId).isNotEqualTo(TelephonyScanManager.INVALID_SCAN_ID);
+    }
+
+    @Test
+    public void testStartNetworkScan_succeed_deathRecipientIsLinked() throws Exception {
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+
+        verify(mIBinder).linkToDeath(any(), anyInt());
+    }
+
+    @Test
+    public void testStartNetworkScan_multipleRequests_scanIdShouldNotRepeat() throws Exception {
+        int firstScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+        stopNetworkScanIfNeeded(firstScanId);
+
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+
+        assertThat(mScanId).isNotEqualTo(firstScanId);
+    }
+
+    @Test
+    public void testStartNetworkScan_singleResultAndSuccess_allMessagesNotified() throws Exception {
+        mMessageLatch = new CountDownLatch(2); // 2 messages expected
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+
+        // Only one result and it is completed.
+        verifyStartNetworkScanAndEmulateScanResult(
+                new NetworkScanResult(NetworkScanResult.SCAN_STATUS_COMPLETE,
+                NetworkScan.SUCCESS, List.of(new CellInfoLte())));
+
+        verifyMessages(
+                // One RESTRICTED_SCAN_RESULTS msg follows by COMPLETE msg
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_RESTRICTED_SCAN_RESULTS,
+                        NetworkScan.SUCCESS, mScanId, null),
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_SCAN_COMPLETE,
+                        NetworkScan.SUCCESS, mScanId, null)
+        );
+    }
+
+    @Test
+    public void testStartNetworkScan_resultFromCopiedRequest_noMessagesNotified() throws Exception {
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+
+        // Scan result came but with copied (instead of original) NetworkScanRequestInfo
+        NetworkScanRequestInfo obsoletedNsri = createNetworkScanRequestInfo(mScanId);
+        verifyStartNetworkScanAndEmulateScanResult(obsoletedNsri,
+                new NetworkScanResult(NetworkScanResult.SCAN_STATUS_COMPLETE,
+                        NetworkScan.SUCCESS, List.of(new CellInfoLte())));
+
+        // No message should send to client
+        verifyMessages();
+    }
+
+    @Test
+    public void testStartNetworkScan_multiResultsAndSuccess_allMessagesNotified() throws Exception {
+        mMessageLatch = new CountDownLatch(4); // 4 messages expected
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+
+        verifyStartNetworkScanAndEmulateScanResult(
+                // First two results arrived but did not complete
+                new NetworkScanResult(NetworkScanResult.SCAN_STATUS_PARTIAL,
+                        NetworkScan.SUCCESS, List.of(new CellInfoLte())),
+                new NetworkScanResult(NetworkScanResult.SCAN_STATUS_PARTIAL,
+                        NetworkScan.SUCCESS, List.of(new CellInfoLte())),
+                // Third result arrived and completed
+                new NetworkScanResult(NetworkScanResult.SCAN_STATUS_COMPLETE,
+                        NetworkScan.SUCCESS, List.of(new CellInfoLte())));
+
+        verifyMessages(
+                // Same number of SCAN_RESULTS and end with COMPLETE messages.
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_RESTRICTED_SCAN_RESULTS,
+                        NetworkScan.SUCCESS, mScanId, null),
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_RESTRICTED_SCAN_RESULTS,
+                        NetworkScan.SUCCESS, mScanId, null),
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_RESTRICTED_SCAN_RESULTS,
+                        NetworkScan.SUCCESS, mScanId, null),
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_SCAN_COMPLETE,
+                        NetworkScan.SUCCESS, mScanId, null)
+        );
+    }
+
+    @Test
+    public void testStartNetworkScan_modemError_allMessagesNotified() throws Exception {
+        mMessageLatch = new CountDownLatch(3); // 3 messages expected
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+
+        verifyStartNetworkScanAndEmulateScanResult(
+                // First result arrived but did not complete
+                new NetworkScanResult(NetworkScanResult.SCAN_STATUS_PARTIAL,
+                        NetworkScan.SUCCESS, List.of(new CellInfoLte())),
+                // Final result arrived and indicated modem error
+                new NetworkScanResult(NetworkScanResult.SCAN_STATUS_COMPLETE,
+                        NetworkScan.ERROR_MODEM_ERROR, List.of(new CellInfoLte())));
+
+        verifyMessages(
+                // First SUCCESS scan result
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_RESTRICTED_SCAN_RESULTS,
+                        NetworkScan.SUCCESS, mScanId, null),
+                // Final ERROR messages
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_RESTRICTED_SCAN_RESULTS,
+                        NetworkScan.ERROR_MODEM_ERROR, mScanId, null),
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_SCAN_ERROR,
+                        NetworkScan.ERROR_MODEM_ERROR, mScanId, null)
+        );
+    }
+
+    @Test
+    public void testStartNetworkScan_clientDied_shouldStopScan() throws Exception {
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+
+        verifyStartNetworkScanAndEmulateBinderDied();
+
+        verify(mPhone).stopNetworkScan(any());
+    }
+
+    @Test
+    public void testStopNetworkScan_invalidScanId_throwIllegalArgumentException() throws Exception {
+        final int invalidScanId = 1000;
+        assertThrows(IllegalArgumentException.class,
+                () -> mNetworkScanRequestTracker.stopNetworkScan(invalidScanId, CLIENT_UID));
+    }
+
+    @Test
+    public void testStopNetworkScan_fromOtherUid_throwIllegalArgumentException() throws Exception {
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mNetworkScanRequestTracker.stopNetworkScan(mScanId, 654321));
+    }
+
+    @Test
+    public void testStopNetworkScan_scanIdAndUidMatchWithoutError_shouldUnregister()
+            throws Exception {
+        mMessageLatch = new CountDownLatch(1); // 1 message expected
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+        mNetworkScanRequestTracker.stopNetworkScan(mScanId, CLIENT_UID);
+        processAllMessages();
+
+        // No error during stopping network scan
+        verifyStopNetworkScanAndEmulateResult(null /* commandException */);
+
+        verify(mPhone.mCi).unregisterForNetworkScanResult(any());
+        verify(mPhone.mCi).unregisterForModemReset(any());
+        verify(mPhone.mCi).unregisterForNotAvailable(any());
+
+        verifyMessages(
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_SCAN_COMPLETE,
+                        NetworkScan.SUCCESS, mScanId, null)
+        );
+    }
+
+    @Test
+    public void testStopNetworkScan_withError_reportTranslatedScanError()
+            throws Exception {
+        mMessageLatch = new CountDownLatch(1); // 1 message expected
+        mScanId = scanNetworkWithOneShot(true /* renounceFineLocationAccess */);
+        mNetworkScanRequestTracker.stopNetworkScan(mScanId, CLIENT_UID);
+        processAllMessages();
+
+        // No memory error during stopping network scan
+        verifyStopNetworkScanAndEmulateResult(
+                new CommandException(CommandException.Error.NO_MEMORY));
+
+        verifyMessages(
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_SCAN_ERROR,
+                        NetworkScan.ERROR_MODEM_ERROR, mScanId, null)
+        );
+    }
+
+    // -- Test cases below cover scenarios when caller has FINE_LOCATION permission --
+    @Test
+    public void testStartNetworkScan_withLocationPermission_allMessagesNotified()
+            throws Exception {
+        setHasLocationPermissions(true);
+        mMessageLatch = new CountDownLatch(2); // 2 messages expected
+        mScanId = scanNetworkWithOneShot(false /* renounceFineLocationAccess */);
+
+        // Only one result and it is completed.
+        verifyStartNetworkScanAndEmulateScanResult(
+                new NetworkScanResult(NetworkScanResult.SCAN_STATUS_COMPLETE,
+                        NetworkScan.SUCCESS, List.of(new CellInfoLte())));
+
+        verifyMessages(
+                // One SCAN_RESULTS msg follows by COMPLETE msg
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_SCAN_RESULTS,
+                        NetworkScan.SUCCESS, mScanId, null),
+                Message.obtain(mHandler, TelephonyScanManager.CALLBACK_SCAN_COMPLETE,
+                        NetworkScan.SUCCESS, mScanId, null)
+        );
+    }
+
+    private void stopNetworkScanIfNeeded(int scanId) {
+        if (scanId != TelephonyScanManager.INVALID_SCAN_ID) {
+            try {
+                mNetworkScanRequestTracker.stopNetworkScan(mScanId, CLIENT_UID);
+                processAllMessages();
+            } catch (IllegalArgumentException ignored) {
+            }
+        }
+    }
+
+    private void verifyMessages(Message... messages) {
+        try {
+            mMessageLatch.await(5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new AssertionError("Interrupted while latch is wait for messages");
+        }
+
+        assertThat(mMessages.size()).isEqualTo(messages.length);
+        for (int i = 0; i < messages.length; i++) {
+            // We only care about what/arg1/arg2/obj by now
+            assertThat(mMessages.get(i).what).isEqualTo(messages[i].what);
+            assertThat(mMessages.get(i).arg1).isEqualTo(messages[i].arg1);
+            assertThat(mMessages.get(i).arg2).isEqualTo(messages[i].arg2);
+            assertThat(mMessages.get(i).obj).isEqualTo(messages[i].obj);
+        }
+    }
+
+    /**
+     * Verify only one Message with better readability
+     */
+    private void verifyMessage(int what, int arg1, int arg2, Object obj) {
+        verifyMessages(Message.obtain(mHandler, what, arg1, arg2, obj));
+    }
+
+
+    /**
+     * Verify both {@link CommandsInterface#startNetworkScan(NetworkScanRequest, Message)} and
+     * {@link CommandsInterface#registerForNetworkScanResult(Handler, int, Object)} and sends
+     * emulated {@link NetworkScanResult} with original NetworkScanRequestInfo back through the
+     * captures.
+     */
+    private void verifyStartNetworkScanAndEmulateScanResult(
+            NetworkScanResult... networkScanResults) {
+        NetworkScanRequestInfo nsri = verifyStartNetworkScanAndGetNetworkScanInfo();
+
+        sendEmulatedScanResult(nsri, networkScanResults);
+    }
+
+    /**
+     * Similar as verifyStartNetworkScanAndEmulateScanResult(NetworkScanResult...) above but
+     * allows to set the customized NetworkScanRequestInfo other than original one.
+     */
+    private void verifyStartNetworkScanAndEmulateScanResult(
+            NetworkScanRequestInfo nsri,
+            NetworkScanResult... networkScanResults) {
+        // Ignore the original NSRI returned
+        verifyStartNetworkScanAndGetNetworkScanInfo();
+
+        sendEmulatedScanResult(nsri, networkScanResults);
+    }
+
+    private void sendEmulatedScanResult(NetworkScanRequestInfo nsri,
+            NetworkScanResult... networkScanResults) {
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        verify(mPhone.mCi).registerForNetworkScanResult(handlerCaptor.capture(), anyInt(), any());
+        Handler handler = handlerCaptor.getValue();
+
+        for (NetworkScanResult networkScanResult : networkScanResults) {
+            Message result = Message.obtain(handler, EVENT_RECEIVE_NETWORK_SCAN_RESULT, nsri);
+            AsyncResult.forMessage(result, networkScanResult, null);
+            result.sendToTarget();
+        }
+        processAllMessages();
+    }
+
+    /**
+     * Verify {@link CommandsInterface#startNetworkScan(NetworkScanRequest, Message)} and emulate
+     * client process died through the captured {@link IBinder.DeathRecipient}.
+     */
+    private void verifyStartNetworkScanAndEmulateBinderDied() {
+        IBinder.DeathRecipient nsri = verifyStartNetworkScanAndGetNetworkScanInfo();
+        nsri.binderDied();
+        processAllMessages();
+    }
+
+    /**
+     * Verify {@link Phone#startNetworkScan(NetworkScanRequest, Message)} and
+     * capture the NetworkScanRequestInfo for further usage.
+     */
+    private NetworkScanRequestInfo verifyStartNetworkScanAndGetNetworkScanInfo() {
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mPhone).startNetworkScan(any(), messageCaptor.capture());
+        Message responseMessage = messageCaptor.getValue();
+        // NetworkScanRequestInfo is not public and can only be treated as IBinder.DeathRecipient
+        NetworkScanRequestInfo nsri = (NetworkScanRequestInfo) responseMessage.obj;
+        AsyncResult.forMessage(responseMessage, nsri, null);
+        responseMessage.sendToTarget();
+        processAllMessages();
+        return nsri;
+    }
+
+    /**
+     * Verify {@link  Phone#stopNetworkScan} and emulate the result (succeed or failures)
+     * through the {@code commandException}.
+     */
+    private void verifyStopNetworkScanAndEmulateResult(CommandException commandException) {
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mPhone).stopNetworkScan(messageCaptor.capture());
+        Message responseMessage = messageCaptor.getValue();
+        // NetworkScanRequestInfo is not public and can only be treated as Object here
+        Object nsri = responseMessage.obj;
+        AsyncResult.forMessage(responseMessage, nsri, commandException);
+        responseMessage.sendToTarget();
+        processAllMessages();
+    }
+
+    private int scanNetworkWithOneShot(boolean renounceFineLocationAccess) {
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN, null,
+                        null)
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers,
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+        int scanId = mNetworkScanRequestTracker.startNetworkScan(renounceFineLocationAccess,
+                request, messenger, mIBinder, mPhone, CLIENT_UID, -1, CLIENT_PKG);
+        processAllMessages();
+
+        return scanId;
+    }
+
+    private NetworkScanRequestInfo createNetworkScanRequestInfo(int scanId) {
+        RadioAccessSpecifier[] specifiers = new RadioAccessSpecifier[]{
+                new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN, null,
+                        null)
+        };
+        NetworkScanRequest request = new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
+                specifiers,
+                5 /* searchPeriodicity */,
+                60 /* maxSearchTime in seconds */,
+                true /* incrementalResults */,
+                5 /* incrementalResultsPeriodicity */,
+                null /* PLMNs */);
+        Messenger messenger = new Messenger(mHandler);
+        return mNetworkScanRequestTracker.new NetworkScanRequestInfo(request, messenger, mIBinder,
+                scanId, mPhone, CLIENT_UID, -1, CLIENT_PKG, true);
+    }
+
+    private void setHasLocationPermissions(boolean hasPermission) {
+        if (!hasPermission) {
+            // System location off, LocationAccessPolicy#checkLocationPermission returns DENIED_SOFT
+            when(mLocationManager.isLocationEnabledForUser(any(UserHandle.class)))
+                    .thenReturn(false);
+        } else {
+            // Turn on all to let LocationAccessPolicy#checkLocationPermission returns ALLOWED
+            when(mContext.checkPermission(eq(Manifest.permission.ACCESS_FINE_LOCATION),
+                    anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+            when(mContext.checkPermission(eq(Manifest.permission.ACCESS_COARSE_LOCATION),
+                    anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+            when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OPSTR_FINE_LOCATION),
+                    anyInt(), anyString(), nullable(String.class), nullable(String.class)))
+                    .thenReturn(AppOpsManager.MODE_ALLOWED);
+            when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OPSTR_COARSE_LOCATION),
+                    anyInt(), anyString(), nullable(String.class), nullable(String.class)))
+                    .thenReturn(AppOpsManager.MODE_ALLOWED);
+            when(mLocationManager.isLocationEnabledForUser(any(UserHandle.class))).thenReturn(true);
+            when(mContext.checkPermission(eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL),
+                    anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index 7f15af8..1ea989a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -42,6 +43,9 @@
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
+import android.telephony.data.EpsQos;
+import android.telephony.data.Qos;
+import android.telephony.data.QosBearerSession;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -57,6 +61,7 @@
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 @RunWith(AndroidTestingRunner.class)
@@ -107,7 +112,8 @@
         // Capture listener to emulate the carrier config change notification used later
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        mNetworkTypeController =
+                new NetworkTypeController(mPhone, mDisplayInfoController, mFeatureFlags);
         processAllMessages();
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
@@ -267,7 +273,7 @@
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -277,13 +283,16 @@
     public void testTransitionToCurrentStateIdleSupportPhysicalChannelConfig1_6() throws Exception {
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        mNetworkTypeController =
+                new NetworkTypeController(mPhone, mDisplayInfoController, mFeatureFlags);
         processAllMessages();
         assertEquals("DefaultState", getCurrentState().getName());
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(false);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -295,14 +304,15 @@
                 CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, true);
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        mNetworkTypeController =
+                new NetworkTypeController(mPhone, mDisplayInfoController, mFeatureFlags);
         sendCarrierConfigChanged();
         processAllMessages();
         assertEquals("DefaultState", getCurrentState().getName());
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -313,7 +323,7 @@
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -324,14 +334,17 @@
             throws Exception {
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        mNetworkTypeController =
+                new NetworkTypeController(mPhone, mDisplayInfoController, mFeatureFlags);
         sendCarrierConfigChanged();
         processAllMessages();
         assertEquals("DefaultState", getCurrentState().getName());
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(true);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -344,20 +357,34 @@
                 CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, true);
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        mNetworkTypeController =
+                new NetworkTypeController(mPhone, mDisplayInfoController, mFeatureFlags);
         sendCarrierConfigChanged();
         processAllMessages();
         assertEquals("DefaultState", getCurrentState().getName());
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
     }
 
     @Test
+    public void testTransitionToCurrentStateNrConnectedIdle() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList();
+        sendCarrierConfigChanged();
+
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+    }
+
+    @Test
     public void testTransitionToCurrentStateNrConnected() throws Exception {
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
@@ -603,32 +630,37 @@
         physicalChannelConfigs.add(pcc2);
         doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
 
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
 
         // bands and bandwidths should stay ratcheted even if an empty PCC list is sent
         doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, new ArrayList<>(), null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
 
         // bands and bandwidths should stay ratcheted as long as anchor NR cell is the same
         physicalChannelConfigs.remove(pcc2);
         doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
 
         // bands and bandwidths should no longer be ratcheted if anchor NR cell changes
         // add pcc3 to front of list to ensure anchor NR cell changes from 1 -> 3
         physicalChannelConfigs.add(0, pcc3);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
 
         physicalChannelConfigs.add(pcc2);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
@@ -662,20 +694,23 @@
         physicalChannelConfigs.add(pcc2);
         doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
 
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
 
         // bands and bandwidths should stay ratcheted even if an empty PCC list is sent
         doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, new ArrayList<>(), null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
 
         // bands and bandwidths should change if PCC list changes
         physicalChannelConfigs.remove(pcc2);
         doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
     }
@@ -711,19 +746,22 @@
         physicalChannelConfigs.add(pcc2);
         doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
 
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
 
         // bands and bandwidths should not stay the same even if an empty PCC list is sent
         doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, new ArrayList<>(), null));
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
 
         // bands and bandwidths should change if PCC list changes
         doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
@@ -734,7 +772,7 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
 
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -745,12 +783,15 @@
             throws Exception {
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        mNetworkTypeController =
+                new NetworkTypeController(mPhone, mDisplayInfoController, mFeatureFlags);
         processAllMessages();
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(true);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         processAllMessages();
@@ -764,13 +805,14 @@
                 CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, true);
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        mNetworkTypeController =
+                new NetworkTypeController(mPhone, mDisplayInfoController, mFeatureFlags);
         sendCarrierConfigChanged();
         processAllMessages();
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         processAllMessages();
@@ -786,7 +828,9 @@
 
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
@@ -804,7 +848,7 @@
         // Transition to LTE connected state
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -814,7 +858,9 @@
         // LTE -> LTE+
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
@@ -832,7 +878,7 @@
         // Transition to idle state
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -842,7 +888,9 @@
         // LTE -> LTE+
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
@@ -854,7 +902,7 @@
         testTransitionToCurrentStateLteConnected();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
 
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -865,12 +913,15 @@
             throws Exception {
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        mNetworkTypeController =
+                new NetworkTypeController(mPhone, mDisplayInfoController, mFeatureFlags);
         processAllMessages();
         testTransitionToCurrentStateLteConnectedSupportPhysicalChannelConfig1_6();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         setPhysicalLinkStatus(false);
-        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null,
+                        mPhone.getServiceStateTracker().getPhysicalChannelConfigList(), null));
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -883,13 +934,14 @@
                 CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, true);
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        mNetworkTypeController =
+                new NetworkTypeController(mPhone, mDisplayInfoController, mFeatureFlags);
         sendCarrierConfigChanged();
         processAllMessages();
         testTransitionToCurrentStateLteConnected_usingUserDataForRrcDetection();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
+                DataCallResponse.LINK_STATUS_DORMANT);
 
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
@@ -910,15 +962,32 @@
 
     @Test
     public void testEventRadioOffOrUnavailable() throws Exception {
-        testTransitionToCurrentStateNrConnected();
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
-                mNetworkTypeController.getOverrideNetworkType());
+        mBundle.putBoolean(CarrierConfigManager.KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL,
+                true);
+        testTransitionToCurrentStateNrConnectedMmwaveWithAdditionalBandAndNoMmwaveNrNsa();
 
+        // Radio off
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(9 /* EVENT_RADIO_OFF_OR_UNAVAILABLE */);
         processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 mNetworkTypeController.getOverrideNetworkType());
+
+        // NR connected: Primary serving NR PCC with cell ID = 1, band = none
+        PhysicalChannelConfig pcc = new PhysicalChannelConfig.Builder()
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setPhysicalCellId(1)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .build();
+
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, List.of(pcc), null));
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
     }
 
     @Test
@@ -970,6 +1039,54 @@
     }
 
     @Test
+    public void testPrimaryTimerNetworkTypeChanged() throws Exception {
+        doAnswer(invocation -> {
+            doReturn(new TelephonyDisplayInfo(
+                    mNetworkTypeController.getDataNetworkType(),
+                    mNetworkTypeController.getOverrideNetworkType(),
+                    false)).when(mDisplayInfoController).getTelephonyDisplayInfo();
+            return null;
+        }).when(mDisplayInfoController).updateTelephonyDisplayInfo();
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        sendCarrierConfigChanged();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // trigger 10 second timer after disconnecting from NR advanced
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
     public void testPrimaryTimerDeviceIdleMode() throws Exception {
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
@@ -1317,6 +1434,320 @@
     }
 
     @Test
+    public void testSecondaryTimerAdvanceBand() throws Exception {
+        doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        ArrayList<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        // use advanced band
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setBand(41)
+                .build());
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41});
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,5");
+        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT,
+                20);
+        sendCarrierConfigChanged();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // lost the advance band, trigger 10 second connected_mmwave -> connected primary timer
+        physicalChannelConfigs.clear();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .build());
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // switch to connected_rrc_idle before primary timer expires
+        physicalChannelConfigs.clear();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 20(not 5) seconds connected_mmwave -> connected_rrc_idle secondary timer
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // 5 seconds passed during connected_mmwave -> connected_rrc_idle secondary timer
+        moveTimeForward(5 * 1000);
+        processAllMessages();
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // secondary timer expired
+        moveTimeForward(15 * 1000);
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
+    public void testSecondaryTimerExpireNrIdle() throws Exception {
+        doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        ArrayList<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setBand(41)
+                .build());
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41});
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        sendCarrierConfigChanged();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        physicalChannelConfigs.clear();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .build());
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // switch to connected_rrc_idle
+        physicalChannelConfigs.clear();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
+    public void testSecondaryTimerResetNrIdle() throws Exception {
+        doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        ArrayList<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setBand(41)
+                .build());
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41});
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        sendCarrierConfigChanged();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        physicalChannelConfigs.clear();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .build());
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // switch to connected_rrc_idle
+        physicalChannelConfigs.clear();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // reconnect to NR in the middle of the timer
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setBand(41)
+                .build());
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        // timer should not have gone off
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
+    public void testSecondaryTimerPrimaryCellChangeNrIdle() throws Exception {
+        doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        ArrayList<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setBand(41)
+                .build());
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41});
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        sendCarrierConfigChanged();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        physicalChannelConfigs.clear();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .build());
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // switch to connected_rrc_idle
+        physicalChannelConfigs.clear();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // primary cell changes
+        physicalChannelConfigs.clear();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(2)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .build());
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
     public void testNrTimerResetIn3g() throws Exception {
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
@@ -1366,7 +1797,7 @@
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+                DataCallResponse.LINK_STATUS_ACTIVE);
         mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
@@ -1432,6 +1863,91 @@
         assertTrue(mNetworkTypeController.areAnyTimersActive());
     }
 
+    @Test
+    public void testNrTimerResetWhenPlmnChanged() throws Exception {
+        testTransitionToCurrentStateNrConnectedMmwave();
+        mBundle.putBoolean(CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL,
+                true);
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        sendCarrierConfigChanged();
+
+        // should trigger 10 second primary timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // PLMN changed, should cancel any active timers
+        ServiceState newSS = mock(ServiceState.class);
+        doReturn("different plmn").when(newSS).getOperatorNumeric();
+        doReturn(newSS).when(mSST).getServiceState();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
+    public void testNrTimerResetWhenVoiceQos() throws Exception {
+        testTransitionToCurrentStateNrConnectedMmwave();
+        mBundle.putBoolean(CarrierConfigManager.KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL,
+                true);
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        sendCarrierConfigChanged();
+
+        // should trigger 10 second primary timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // Qos changed, but not in call, so no thing happens.
+        ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
+                ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
+        verify(mDataNetworkController).registerDataNetworkControllerCallback(
+                dataNetworkControllerCallbackCaptor.capture());
+        DataNetworkControllerCallback callback = dataNetworkControllerCallbackCaptor.getValue();
+        callback.onQosSessionsChanged(List.of(
+                new QosBearerSession(1, new EpsQos(
+                        new Qos.QosBandwidth(1000, 1),
+                        new Qos.QosBandwidth(1000, 0),
+                        9 /* QCI */), Collections.emptyList())));
+        processAllMessages();
+
+        // Confirm if QCI not 1, timers are still active.
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // Send Voice QOS where QCI is 1, confirm timers are cancelled.
+        callback.onQosSessionsChanged(List.of(
+                new QosBearerSession(1, new EpsQos(
+                        new Qos.QosBandwidth(1000, 1),
+                        new Qos.QosBandwidth(1000, 0),
+                        1 /* QCI */), Collections.emptyList())));
+        processAllMessages();
+
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
     private void setPhysicalLinkStatus(boolean state) {
         List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>();
         // If PhysicalChannelConfigList is empty, PhysicalLinkStatus is
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneCapabilityTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneCapabilityTest.java
index 2410625..ca9d94a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneCapabilityTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneCapabilityTest.java
@@ -23,7 +23,8 @@
 import android.os.Parcel;
 import android.telephony.ModemInfo;
 import android.telephony.PhoneCapability;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
index 700a246..6743d1c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
@@ -20,6 +20,7 @@
 import static android.telephony.TelephonyManager.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -37,12 +38,17 @@
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
+import android.telephony.ModemInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionManager;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.telephony.TelephonyRegistryManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.telephony.flags.FeatureFlags;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -50,6 +56,13 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class PhoneConfigurationManagerTest extends TelephonyTest {
@@ -61,7 +74,23 @@
     PhoneConfigurationManager.MockableInterface mMi;
 
     private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 1;
+    private static final PhoneCapability STATIC_DSDA_CAPABILITY;
     PhoneConfigurationManager mPcm;
+    private FeatureFlags mFeatureFlags;
+    private TelephonyRegistryManager mMockRegistryManager;
+
+    static {
+        ModemInfo modemInfo1 = new ModemInfo(0, 0, true, true);
+        ModemInfo modemInfo2 = new ModemInfo(1, 0, true, true);
+
+        List<ModemInfo> logicalModemList = new ArrayList<>();
+        logicalModemList.add(modemInfo1);
+        logicalModemList.add(modemInfo2);
+        int[] deviceNrCapabilities = new int[0];
+
+        STATIC_DSDA_CAPABILITY = new PhoneCapability(2, 1, logicalModemList, false,
+                deviceNrCapabilities);
+    }
 
     @Before
     public void setUp() throws Exception {
@@ -69,11 +98,14 @@
         mHandler = mock(Handler.class);
         mMockCi0 = mock(CommandsInterface.class);
         mMockCi1 = mock(CommandsInterface.class);
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
         mPhone1 = mock(Phone.class);
         mMi = mock(PhoneConfigurationManager.MockableInterface.class);
         mPhone.mCi = mMockCi0;
         mCT.mCi = mMockCi0;
         mPhone1.mCi = mMockCi1;
+        doReturn(RIL.RADIO_HAL_VERSION_2_2).when(mMockRadioConfigProxy).getVersion();
+        mMockRegistryManager = mContext.getSystemService(TelephonyRegistryManager.class);
     }
 
     @After
@@ -89,7 +121,7 @@
     private void init(int numOfSim) throws Exception {
         doReturn(numOfSim).when(mTelephonyManager).getActiveModemCount();
         replaceInstance(PhoneConfigurationManager.class, "sInstance", null, null);
-        mPcm = PhoneConfigurationManager.init(mContext);
+        mPcm = PhoneConfigurationManager.init(mContext, mFeatureFlags);
         replaceInstance(PhoneConfigurationManager.class, "mMi", mPcm, mMi);
         processAllMessages();
     }
@@ -125,15 +157,7 @@
         init(1);
         assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability());
 
-        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
-        verify(mMockRadioConfig).getPhoneCapability(captor.capture());
-        Message msg = captor.getValue();
-        AsyncResult.forMessage(msg, PhoneCapability.DEFAULT_DSDS_CAPABILITY, null);
-        msg.sendToTarget();
-        processAllMessages();
-
-        // Not static capability should indicate DSDS capable.
-        assertEquals(PhoneCapability.DEFAULT_DSDS_CAPABILITY, mPcm.getStaticPhoneCapability());
+        setAndVerifyStaticCapability(PhoneCapability.DEFAULT_DSDS_CAPABILITY);
     }
 
     @Test
@@ -159,6 +183,209 @@
 
     @Test
     @SmallTest
+    public void testUpdateSimultaneousCallingSupport() throws Exception {
+        doReturn(false).when(mFeatureFlags).simultaneousCallingIndications();
+        init(2);
+        mPcm.updateSimultaneousCallingSupport();
+
+        int[] enabledLogicalSlots = {0, 1};
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
+        Message msg = captor.getValue();
+        AsyncResult.forMessage(msg, enabledLogicalSlots, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        HashSet<Integer> expectedSlots = new HashSet<>();
+        for (int i : enabledLogicalSlots) { expectedSlots.add(i); }
+        assertEquals(expectedSlots, mPcm.getSlotsSupportingSimultaneousCellularCalls());
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateSimultaneousCallingSupport_invalidResponse_shouldFail() throws Exception {
+        doReturn(false).when(mFeatureFlags).simultaneousCallingIndications();
+        init(2);
+        mPcm.updateSimultaneousCallingSupport();
+
+        // Have the modem send invalid phone slots -1 and 5:
+        int[] invalidEnabledLogicalSlots = {-1, 5};
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
+        Message msg = captor.getValue();
+        AsyncResult.forMessage(msg, invalidEnabledLogicalSlots, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // We would expect to DSDA to be disabled and mSlotsSupportingSimultaneousCellularCalls to
+        // have been cleared:
+        assertTrue(mPcm.getSlotsSupportingSimultaneousCellularCalls().isEmpty());
+    }
+
+    /**
+     * If the device uses the older "dsda" multi_sim_config setting, ensure that DSDA is set
+     * statically for that device and subId updates work.
+     */
+    @Test
+    @SmallTest
+    public void testBkwdsCompatSimultaneousCallingDsda() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+        doReturn(RIL.RADIO_HAL_VERSION_2_1).when(mMockRadioConfigProxy).getVersion();
+        doReturn(Optional.of("dsda")).when(mMi).getMultiSimProperty();
+        final int phone0SubId = 2;
+        final int phone1SubId = 3;
+        mPhones = new Phone[]{mPhone, mPhone1};
+        doReturn(0).when(mPhone).getPhoneId();
+        doReturn(1).when(mPhone1).getPhoneId();
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        init(2);
+        doReturn(phone0SubId).when(mPhone).getSubId();
+        doReturn(phone1SubId).when(mPhone1).getSubId();
+        Set<Integer>[] cachedSimultaneousCallingSlots = new Set[]{Collections.emptySet()};
+        mPcm.registerForSimultaneousCellularCallingSlotsChanged(newSlots ->
+                cachedSimultaneousCallingSlots[0] = newSlots);
+
+        mPcm.getStaticPhoneCapability();
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+        ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> cBCaptor =
+                ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
+        verify(mMockRegistryManager).addOnSubscriptionsChangedListener(cBCaptor.capture(), any());
+        processAllMessages();
+
+        int[] enabledLogicalSlots = {0, 1};
+        HashSet<Integer> expectedSlots = new HashSet<>(2);
+        for (int i : enabledLogicalSlots) {
+            expectedSlots.add(i);
+        }
+        HashSet<Integer> expectedSubIds = new HashSet<>(2);
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubId);
+        assertEquals(expectedSlots, mPcm.getSlotsSupportingSimultaneousCellularCalls());
+        verify(mMockRegistryManager).notifySimultaneousCellularCallingSubscriptionsChanged(
+                eq(expectedSubIds));
+        assertEquals(expectedSlots, cachedSimultaneousCallingSlots[0]);
+
+        // Change sub ID mapping
+        final int phone1SubIdV2 = 4;
+        expectedSubIds.clear();
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubIdV2);
+        doReturn(phone1SubIdV2).when(mPhone1).getSubId();
+        cBCaptor.getValue().onSubscriptionsChanged();
+        processAllMessages();
+        verify(mMockRegistryManager, times(2))
+                .notifySimultaneousCellularCallingSubscriptionsChanged(eq(expectedSubIds));
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateSimultaneousCallingSupportNotifications() throws Exception {
+        // retry simultaneous calling tests, but with notifications enabled this time
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+
+        final int phone0SubId = 2;
+        final int phone1SubId = 3;
+        mPhones = new Phone[]{mPhone, mPhone1};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        init(2);
+        doReturn(phone0SubId).when(mPhone).getSubId();
+        doReturn(phone1SubId).when(mPhone1).getSubId();
+        Set<Integer>[] cachedSimultaneousCallingSlots = new Set[]{Collections.emptySet()};
+        mPcm.registerForSimultaneousCellularCallingSlotsChanged(newSlots ->
+                cachedSimultaneousCallingSlots[0] = newSlots);
+
+        // Simultaneous calling enabled
+        mPcm.updateSimultaneousCallingSupport();
+        int[] enabledLogicalSlots = {0, 1};
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
+        Message msg = captor.getValue();
+        AsyncResult.forMessage(msg, enabledLogicalSlots, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        HashSet<Integer> expectedSlots = new HashSet<>(2);
+        for (int i : enabledLogicalSlots) {
+            expectedSlots.add(i);
+        }
+        HashSet<Integer> expectedSubIds = new HashSet<>(2);
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubId);
+        assertEquals(expectedSlots, mPcm.getSlotsSupportingSimultaneousCellularCalls());
+        verify(mMockRegistryManager).notifySimultaneousCellularCallingSubscriptionsChanged(
+                eq(expectedSubIds));
+        assertEquals(expectedSlots, cachedSimultaneousCallingSlots[0]);
+
+        // Simultaneous Calling Disabled
+        mPcm.updateSimultaneousCallingSupport();
+        int[] disabled = {};
+        captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig, times(2)).updateSimultaneousCallingSupport(captor.capture());
+        msg = captor.getAllValues().get(1);
+        AsyncResult.forMessage(msg, disabled, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        assertEquals(Collections.emptySet(), mPcm.getSlotsSupportingSimultaneousCellularCalls());
+        verify(mMockRegistryManager, times(2))
+                .notifySimultaneousCellularCallingSubscriptionsChanged(eq(Collections.emptySet()));
+        assertEquals(Collections.emptySet(), cachedSimultaneousCallingSlots[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void testSimultaneousCallingSubIdMappingChanges() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+        final int phone0SubId = 2;
+        final int phone1SubId = 3;
+        mPhones = new Phone[]{mPhone, mPhone1};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        init(2);
+        doReturn(phone0SubId).when(mPhone).getSubId();
+        doReturn(phone1SubId).when(mPhone1).getSubId();
+
+        // Set the capability to DSDA mode to register listener, which will also trigger
+        // simultaneous calling evaluation
+        mPcm.getCurrentPhoneCapability();
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+        ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> cBCaptor =
+                ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
+        verify(mMockRegistryManager).addOnSubscriptionsChangedListener(cBCaptor.capture(), any());
+
+        int[] enabledLogicalSlots = {0, 1};
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
+        Message msg = captor.getValue();
+        // Simultaneous calling enabled
+        AsyncResult.forMessage(msg, enabledLogicalSlots, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        HashSet<Integer> expectedSlots = new HashSet<>(2);
+        for (int i : enabledLogicalSlots) {
+            expectedSlots.add(i);
+        }
+        HashSet<Integer> expectedSubIds = new HashSet<>(2);
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubId);
+        assertEquals(expectedSlots, mPcm.getSlotsSupportingSimultaneousCellularCalls());
+        verify(mMockRegistryManager).notifySimultaneousCellularCallingSubscriptionsChanged(
+                eq(expectedSubIds));
+
+        // Change sub ID mapping
+        final int phone1SubIdV2 = 4;
+        expectedSubIds.clear();
+        expectedSubIds.add(phone0SubId);
+        expectedSubIds.add(phone1SubIdV2);
+        doReturn(phone1SubIdV2).when(mPhone1).getSubId();
+        cBCaptor.getValue().onSubscriptionsChanged();
+        processAllMessages();
+        verify(mMockRegistryManager, times(2))
+                .notifySimultaneousCellularCallingSubscriptionsChanged(eq(expectedSubIds));
+    }
+
+    @Test
+    @SmallTest
     public void testSwitchMultiSimConfig_notDsdsCapable_shouldFail() throws Exception {
         init(1);
         assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability());
@@ -335,4 +562,15 @@
         verify(mMockCi1, times(1)).registerForAvailable(any(), anyInt(), any());
         verify(mMockCi1, times(1)).onSlotActiveStatusChange(anyBoolean());
     }
+
+    private void setAndVerifyStaticCapability(PhoneCapability capability) {
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).getPhoneCapability(captor.capture());
+        Message msg = captor.getValue();
+        AsyncResult.forMessage(msg, capability, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        assertEquals(capability, mPcm.getStaticPhoneCapability());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index 2f3bbf7..1c4e43d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -23,11 +23,11 @@
 
 import android.net.Uri;
 import android.telephony.PhoneNumberUtils;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.SpannableStringBuilder;
 import android.text.style.TtsSpan;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -216,6 +216,12 @@
         assertNull(PhoneNumberUtils.toCallerIDMinMatch(null));
         assertNull(PhoneNumberUtils.getStrippedReversed(null));
         assertNull(PhoneNumberUtils.stringFromStringAndTOA(null, 1));
+
+        // Test for known potential bad extraction of post dial portion.
+        assertEquals(";123456",
+                PhoneNumberUtils.extractPostDialPortion("6281769222;123456"));
+        assertEquals("6281769222", PhoneNumberUtils.extractNetworkPortion(
+                "6281769222;123456"));
     }
 
     @SmallTest
@@ -856,4 +862,20 @@
         assertEquals(TtsSpan.TYPE_TELEPHONE, ttsSpan.getType());
         assertEquals(expected, ttsSpan.getArgs().getString(TtsSpan.ARG_NUMBER_PARTS));
     }
+
+    @SmallTest
+    @Test
+    public void testWpsCallNumber() {
+        // Test number without special symbols.
+        assertFalse(PhoneNumberUtils.isWpsCallNumber("12345678"));
+
+        // TTS number should not be recognized as wps.
+        assertFalse(PhoneNumberUtils.isWpsCallNumber("*23212345678"));
+        assertFalse(PhoneNumberUtils.isWpsCallNumber("*232#12345678"));
+
+        // Check WPS valid numbers
+        assertTrue(PhoneNumberUtils.isWpsCallNumber("*27212345678"));
+        assertTrue(PhoneNumberUtils.isWpsCallNumber("*31#*27212345678"));
+        assertTrue(PhoneNumberUtils.isWpsCallNumber("#31#*27212345678"));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
index c10dea9..e04507c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
@@ -18,12 +18,14 @@
 import static org.junit.Assert.assertEquals;
 
 import android.telephony.PhoneNumberFormattingTextWatcher;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.Editable;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextWatcher;
 import android.text.style.TtsSpan;
+
+import androidx.test.filters.SmallTest;
+
 import org.junit.Ignore;
 import org.junit.Test;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java
index 5cd54c1..afc6f4a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java
@@ -21,7 +21,8 @@
 
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java
index 48b49d1..730e20f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java
@@ -24,10 +24,11 @@
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.emergency.EmergencyNumber;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
index 00634a0..1af4a76 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
@@ -23,8 +23,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -33,12 +35,14 @@
 
 import android.app.AppOpsManager;
 import android.app.PropertyInvalidatedCache;
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
+import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telephony.uicc.IsimUiccRecords;
@@ -47,10 +51,14 @@
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.UiccProfile;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.mockito.Mockito;
 
 import java.util.List;
@@ -62,6 +70,9 @@
     private static final String PSI_SMSC_TEL2 = "tel:+91987654321";
     private static final String PSI_SMSC_SIP2 = "sip:+19876543210@dcf.pc.operetor2.com;user=phone";
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     private PhoneSubInfoController mPhoneSubInfoControllerUT;
     private AppOpsManager mAppOsMgr;
     private PackageManager mPm;
@@ -91,7 +102,7 @@
         mPm = mContext.getPackageManager();
 
         replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mSecondPhone});
-        mPhoneSubInfoControllerUT = new PhoneSubInfoController(mContext);
+        mPhoneSubInfoControllerUT = new PhoneSubInfoController(mContext, mFeatureFlags);
 
         setupMocksForTelephonyPermissions();
         // TelephonyPermissions will query the READ_DEVICE_IDENTIFIERS op from AppOpManager to
@@ -104,6 +115,12 @@
 
         // Bypass calling package check.
         doReturn(Binder.getCallingUid()).when(mPm).getPackageUid(eq(TAG), anyInt());
+
+        // In order not to affect the existing implementation, define a telephony features
+        // and disabled enforce_telephony_feature_mapping_for_public_apis feature flag
+        doReturn(false).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+        doReturn(true).when(mPm).hasSystemFeature(anyString());
+        doReturn(new String[] {TAG}).when(mPm).getPackagesForUid(anyInt());
     }
 
     @After
@@ -156,7 +173,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -178,7 +195,7 @@
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         // The READ_PRIVILEGED_PHONE_STATE permission is now required to get device identifiers.
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -212,6 +229,31 @@
 
     @Test
     @SmallTest
+    @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
+    public void testGetNai_EnabledEnforceTelephonyFeatureMappingForPublicApis() {
+        // FeatureFlags enabled, System has required feature
+        doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+        doReturn(true).when(mPm).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+        doReturn("bbb@example.com").when(mSecondPhone).getNai();
+
+        // Enabled FeatureFlags and ENABLE_FEATURE_MAPPING, telephony features are defined
+        try {
+            assertEquals("bbb@example.com",
+                    mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG, FEATURE_ID));
+        } catch (UnsupportedOperationException e) {
+            fail("Not expect exception " + e.getMessage());
+        }
+
+        // Telephony features is not defined, expect UnsupportedOperationException.
+        doReturn(false).when(mPm).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+        assertThrows(UnsupportedOperationException.class,
+                () -> mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG, FEATURE_ID));
+    }
+
+    @Test
+    @SmallTest
     public void testGetNaiWithOutPermission() {
         // The READ_PRIVILEGED_PHONE_STATE permission, carrier privileges, or passing a device /
         // profile owner access check is required to access subscriber identifiers. Since none of
@@ -240,7 +282,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -261,7 +303,7 @@
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -323,7 +365,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -344,7 +386,7 @@
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -400,7 +442,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
 
@@ -409,7 +451,7 @@
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         assertEquals("00", mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(0, TAG, FEATURE_ID));
@@ -466,7 +508,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -488,7 +530,7 @@
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         // The READ_PRIVILEGED_PHONE_STATE permission is now required to get device identifiers.
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -551,7 +593,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -572,7 +614,7 @@
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         try {
@@ -718,7 +760,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
 
@@ -727,7 +769,7 @@
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         assertEquals("LINE1_SIM_0", mPhoneSubInfoControllerUT
@@ -865,7 +907,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
 
@@ -874,7 +916,7 @@
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         assertEquals("+18051234567", mPhoneSubInfoControllerUT
@@ -921,7 +963,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
 
@@ -930,7 +972,7 @@
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
         assertEquals("VM_SIM_0", mPhoneSubInfoControllerUT
@@ -1058,7 +1100,7 @@
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
 
@@ -1086,7 +1128,7 @@
 
         doReturn(refSst).when(mSimRecords).getSimServiceTable();
 
-        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(0, 0);
         assertEquals(refSst, resultSst);
     }
 
@@ -1100,22 +1142,22 @@
 
         doReturn(refSst).when(mSimRecords).getSimServiceTable();
 
-        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(0, 0);
         assertEquals(refSst, resultSst);
     }
 
     @Test
     public void testGetSstWhenNoUiccPort() throws RemoteException {
-            String refSst = "1234567";
-            doReturn(null).when(mPhone).getUiccPort();
-            doReturn(mUiccProfile).when(mUiccPort).getUiccProfile();
-            doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt());
-            doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords();
+        String refSst = "1234567";
+        doReturn(null).when(mPhone).getUiccPort();
+        doReturn(mUiccProfile).when(mUiccPort).getUiccProfile();
+        doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt());
+        doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords();
 
-            doReturn(refSst).when(mSimRecords).getSimServiceTable();
+        doReturn(refSst).when(mSimRecords).getSimServiceTable();
 
-            String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
-            assertEquals(null, resultSst);
+        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(0, 0);
+        assertEquals(null, resultSst);
     }
 
     @Test
@@ -1128,7 +1170,7 @@
 
         doReturn(refSst).when(mSimRecords).getSimServiceTable();
 
-        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(0, 0);
         assertEquals(null, resultSst);
     }
 
@@ -1142,7 +1184,7 @@
 
         doReturn(refSst).when(mSimRecords).getSimServiceTable();
 
-        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(0, 0);
         assertEquals(null, resultSst);
     }
 
@@ -1158,7 +1200,7 @@
 
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+            mPhoneSubInfoControllerUT.getSimServiceTable(0, 0);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -1166,7 +1208,7 @@
         }
 
         mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
-        assertEquals(refSst, mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt()));
+        assertEquals(refSst, mPhoneSubInfoControllerUT.getSimServiceTable(0, 0));
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PlmnActRecordTest.java b/tests/telephonytests/src/com/android/internal/telephony/PlmnActRecordTest.java
index 6769105..57a49d2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PlmnActRecordTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PlmnActRecordTest.java
@@ -20,7 +20,8 @@
 
 import android.os.Parcel;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import java.util.Arrays;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java
index 65ab664..50061c6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java
@@ -35,10 +35,11 @@
 import android.os.Handler;
 import android.os.Message;
 import android.telephony.RadioAccessFamily;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -55,7 +56,7 @@
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         replaceInstance(ProxyController.class, "sProxyController", null, null);
-        mProxyController = ProxyController.getInstance(mContext);
+        mProxyController = new ProxyController(mContext, mFeatureFlags);
     }
 
     @After
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index 2396d1d..bfe9649 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -64,7 +64,6 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_NV_RESET_CONFIG;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_NV_WRITE_ITEM;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_OPERATOR;
-import static com.android.internal.telephony.RILConstants.RIL_REQUEST_PULL_LCEDATA;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_REPORT_SMS_MEMORY_STATUS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING;
@@ -82,10 +81,8 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_AUTHENTICATION;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_CLOSE_CHANNEL;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_OPEN_CHANNEL;
-import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_LCE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_NETWORK_SCAN;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM;
-import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_LCE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_UDUB;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_VOICE_RADIO_TECH;
@@ -105,17 +102,18 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.hardware.radio.V1_0.Carrier;
 import android.hardware.radio.V1_0.CdmaSmsMessage;
-import android.hardware.radio.V1_0.DataProfileInfo;
 import android.hardware.radio.V1_0.GsmSmsMessage;
 import android.hardware.radio.V1_0.ImsSmsMessage;
 import android.hardware.radio.V1_0.NvWriteItem;
@@ -130,6 +128,7 @@
 import android.net.LinkAddress;
 import android.os.AsyncResult;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IPowerManager;
 import android.os.IThermalService;
 import android.os.Looper;
@@ -139,6 +138,7 @@
 import android.os.WorkSource;
 import android.service.carrier.CarrierIdentifier;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.CellConfigLte;
 import android.telephony.CellIdentityCdma;
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellIdentityLte;
@@ -158,11 +158,10 @@
 import android.telephony.CellSignalStrengthNr;
 import android.telephony.CellSignalStrengthTdscdma;
 import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.ClosedSubscriberGroupInfo;
 import android.telephony.NetworkScanRequest;
 import android.telephony.RadioAccessFamily;
 import android.telephony.RadioAccessSpecifier;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
 import android.telephony.SmsManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -199,6 +198,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
 @RunWith(AndroidTestingRunner.class)
@@ -220,13 +221,10 @@
     private RadioSimProxy mSimProxy;
     private RadioModemProxy mRadioModemProxy;
 
-    private Map<Integer, HalVersion> mHalVersionV10 = new HashMap<>();
-    private Map<Integer, HalVersion> mHalVersionV11 = new HashMap<>();
-    private Map<Integer, HalVersion> mHalVersionV12 = new HashMap<>();
-    private Map<Integer, HalVersion> mHalVersionV13 = new HashMap<>();
     private Map<Integer, HalVersion> mHalVersionV14 = new HashMap<>();
     private Map<Integer, HalVersion> mHalVersionV15 = new HashMap<>();
     private Map<Integer, HalVersion> mHalVersionV16 = new HashMap<>();
+    private Map<Integer, HalVersion> mHalVersionV20 = new HashMap<>();
     private Map<Integer, HalVersion> mHalVersionV21 = new HashMap<>();
 
     private RIL mRILInstance;
@@ -243,25 +241,21 @@
     private static final int CI = 268435456;
     private static final int CID = 65535;
     private static final int CQI = 2147483647;
+    private static final int CQI_TABLE_INDEX = 1;
     private static final int DBM = -74;
     private static final int EARFCN = 262140;
-    private static final List<Integer> BANDS = Arrays.asList(1, 2);
+    private static final ArrayList<Integer> BANDS = new ArrayList<>(Arrays.asList(1, 2));
     private static final int BANDWIDTH = 5000;
     private static final int ECIO = -124;
-    private static final String EMPTY_ALPHA_LONG = "";
-    private static final String EMPTY_ALPHA_SHORT = "";
     private static final int LAC = 65535;
     private static final int LATITUDE = 1292000;
     private static final int LONGITUDE = 1295000;
-    private static final int MCC = 120;
     private static final String MCC_STR = "120";
-    private static final int MNC = 260;
     private static final String MNC_STR = "260";
     private static final int NETWORK_ID = 65534;
     private static final int NRARFCN = 3279165;
     private static final int PCI = 503;
     private static final int PSC = 500;
-    private static final int RIL_TIMESTAMP_TYPE_OEM_RIL = 3;
     private static final int RSSNR = CellInfo.UNAVAILABLE;
     private static final int RSRP = -96;
     private static final int RSRQ = -10;
@@ -277,11 +271,9 @@
     private static final int TIMING_ADVANCE = 4;
     private static final long TIMESTAMP = 215924934;
     private static final int UARFCN = 690;
-    private static final int TYPE_CDMA = 2;
-    private static final int TYPE_GSM = 1;
-    private static final int TYPE_LTE = 3;
-    private static final int TYPE_WCDMA = 4;
-    private static final int TYPE_TD_SCDMA = 5;
+    private static final int CONNECTION_STATUS = CellInfo.CONNECTION_NONE;
+    private static final boolean ENDC_AVAILABLE = true;
+    private static final boolean REGISTERED = true;
 
     private static final int PROFILE_ID = 0;
     private static final String APN = "apn";
@@ -298,7 +290,6 @@
                     | TelephonyManager.NETWORK_TYPE_BITMASK_LTE);
     private static final int ROAMING_PROTOCOL = ApnSetting.PROTOCOL_IPV6;
     private static final int MTU = 1234;
-    private static final boolean PERSISTENT = true;
 
     private static final String[] ADDITIONAL_PLMNS = new String[] {"00101", "001001", "12345"};
 
@@ -341,31 +332,26 @@
                 RadioAccessFamily.getRafFromNetworkType(RILConstants.PREFERRED_NETWORK_MODE),
                 Phone.PREFERRED_CDMA_SUBSCRIPTION, 0, proxies);
         mRILUnderTest = spy(mRILInstance);
-        doReturn(mRadioProxy).when(mRILUnderTest).getRadioProxy(any());
-        doReturn(mDataProxy).when(mRILUnderTest).getRadioServiceProxy(eq(RadioDataProxy.class),
-                any());
+        doReturn(mRadioProxy).when(mRILUnderTest).getRadioProxy();
+        doReturn(mDataProxy).when(mRILUnderTest).getRadioServiceProxy(eq(RadioDataProxy.class));
         doReturn(mNetworkProxy).when(mRILUnderTest).getRadioServiceProxy(
-                eq(RadioNetworkProxy.class), any());
-        doReturn(mSimProxy).when(mRILUnderTest).getRadioServiceProxy(eq(RadioSimProxy.class),
-                any());
+                eq(RadioNetworkProxy.class));
+        doReturn(mSimProxy).when(mRILUnderTest).getRadioServiceProxy(eq(RadioSimProxy.class));
         doReturn(mRadioModemProxy).when(mRILUnderTest).getRadioServiceProxy(
-                eq(RadioModemProxy.class), any());
+                eq(RadioModemProxy.class));
         doReturn(false).when(mDataProxy).isEmpty();
         doReturn(false).when(mNetworkProxy).isEmpty();
         doReturn(false).when(mSimProxy).isEmpty();
         doReturn(false).when(mRadioModemProxy).isEmpty();
         try {
             for (int service = RIL.MIN_SERVICE_IDX; service <= RIL.MAX_SERVICE_IDX; service++) {
-                mHalVersionV10.put(service, new HalVersion(1, 0));
-                mHalVersionV11.put(service, new HalVersion(1, 1));
-                mHalVersionV12.put(service, new HalVersion(1, 2));
-                mHalVersionV13.put(service, new HalVersion(1, 3));
                 mHalVersionV14.put(service, new HalVersion(1, 4));
                 mHalVersionV15.put(service, new HalVersion(1, 5));
                 mHalVersionV16.put(service, new HalVersion(1, 6));
+                mHalVersionV20.put(service, new HalVersion(2, 0));
                 mHalVersionV21.put(service, new HalVersion(2, 1));
             }
-            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV10);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV14);
         } catch (Exception e) {
         }
     }
@@ -511,7 +497,6 @@
     @FlakyTest
     @Test
     public void testSupplySimDepersonalization() throws Exception {
-
         String controlKey = "1234";
         PersoSubState persoType = PersoSubState.PERSOSUBSTATE_SIM_NETWORK_PUK;
 
@@ -530,11 +515,7 @@
         mRILUnderTest.supplySimDepersonalization(persoType, controlKey, obtainMessage());
         verify(mRadioProxy).supplySimDepersonalization(
                 mSerialNumberCaptor.capture(),
-                eq((int) invokeMethod(
-                        mRILInstance,
-                        "convertPersoTypeToHalPersoType",
-                        new Class<?>[] {PersoSubState.class},
-                        new Object[] {persoType})),
+                RILUtils.convertToHalPersoType(persoType),
                 eq(controlKey));
         verifyRILResponse(
                 mRILUnderTest,
@@ -568,11 +549,7 @@
         mRILUnderTest.supplySimDepersonalization(persoType, controlKey, obtainMessage());
         verify(mRadioProxy).supplySimDepersonalization(
                 mSerialNumberCaptor.capture(),
-                eq((int) invokeMethod(
-                        mRILInstance,
-                        "convertPersoTypeToHalPersoType",
-                        new Class<?>[] {PersoSubState.class},
-                        new Object[] {persoType})),
+                RILUtils.convertToHalPersoType(persoType),
                 eq(controlKey));
         verifyRILResponse(
                 mRILUnderTest,
@@ -1166,18 +1143,11 @@
                 .setApnSetting(apnSetting)
                 .setPreferred(false)
                 .build();
-        boolean isRoaming = false;
 
-        mRILUnderTest.setInitialAttachApn(dataProfile, isRoaming, obtainMessage());
-        verify(mRadioProxy).setInitialAttachApn(
+        mRILUnderTest.setInitialAttachApn(dataProfile, obtainMessage());
+        verify(mRadioProxy).setInitialAttachApn_1_4(
                 mSerialNumberCaptor.capture(),
-                eq((DataProfileInfo) invokeMethod(
-                        mRILInstance,
-                        "convertToHalDataProfile10",
-                        new Class<?>[] {DataProfile.class},
-                        new Object[] {dataProfile})),
-                eq(dataProfile.isPersistent()),
-                eq(isRoaming));
+                eq(RILUtils.convertToHalDataProfile14(dataProfile)));
         verifyRILResponse(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_SET_INITIAL_ATTACH_APN);
     }
@@ -1318,11 +1288,7 @@
         mRILUnderTest.nvResetConfig(resetType, obtainMessage());
         verify(mRadioProxy).nvResetConfig(
                 mSerialNumberCaptor.capture(),
-                eq((Integer) invokeMethod(
-                        mRILInstance,
-                        "convertToHalResetNvType",
-                        new Class<?>[] {Integer.TYPE},
-                        new Object[] {resetType})));
+                RILUtils.convertToHalResetNvType(resetType));
         verifyRILResponse(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_NV_RESET_CONFIG);
     }
@@ -1377,33 +1343,6 @@
 
     @FlakyTest
     @Test
-    public void testStartLceService() throws Exception {
-        int reportIntervalMs = 1000;
-        boolean pullMode = false;
-        mRILUnderTest.startLceService(reportIntervalMs, pullMode, obtainMessage());
-        verify(mRadioProxy).startLceService(
-                mSerialNumberCaptor.capture(), eq(reportIntervalMs), eq(pullMode));
-        verifyRILResponse(mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_START_LCE);
-    }
-
-    @FlakyTest
-    @Test
-    public void testStopLceService() throws Exception {
-        mRILUnderTest.stopLceService(obtainMessage());
-        verify(mRadioProxy).stopLceService(mSerialNumberCaptor.capture());
-        verifyRILResponse(mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_STOP_LCE);
-    }
-
-    @FlakyTest
-    @Test
-    public void testPullLceData() throws Exception {
-        mRILUnderTest.pullLceData(obtainMessage());
-        verify(mRadioProxy).pullLceData(mSerialNumberCaptor.capture());
-        verifyRILResponse(mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_PULL_LCEDATA);
-    }
-
-    @FlakyTest
-    @Test
     public void testGetModemActivityInfo() throws Exception {
         mRILUnderTest.getModemActivityInfo(obtainMessage(), new WorkSource());
         verify(mRadioProxy).getModemActivityInfo(mSerialNumberCaptor.capture());
@@ -1606,460 +1545,694 @@
         return respInfo;
     }
 
+    private android.hardware.radio.V1_2.CellIdentityOperatorNames getCellIdentityOperatorNames() {
+        android.hardware.radio.V1_2.CellIdentityOperatorNames operatorNames =
+                new android.hardware.radio.V1_2.CellIdentityOperatorNames();
+        operatorNames.alphaLong = ALPHA_LONG;
+        operatorNames.alphaShort = ALPHA_SHORT;
+
+        return operatorNames;
+    }
+
+    private android.hardware.radio.V1_2.CellIdentityLte getCellIdentityLte_1_2() {
+        android.hardware.radio.V1_0.CellIdentityLte cellIdentity0 =
+                new android.hardware.radio.V1_0.CellIdentityLte();
+        cellIdentity0.mcc = MCC_STR;
+        cellIdentity0.mnc = MNC_STR;
+        cellIdentity0.ci = CI;
+        cellIdentity0.pci = PCI;
+        cellIdentity0.tac = TAC;
+        cellIdentity0.earfcn = EARFCN;
+
+        android.hardware.radio.V1_2.CellIdentityLte cellIdentity =
+                new android.hardware.radio.V1_2.CellIdentityLte();
+        cellIdentity.base = cellIdentity0;
+        cellIdentity.operatorNames = getCellIdentityOperatorNames();
+        cellIdentity.bandwidth = BANDWIDTH;
+
+        return cellIdentity;
+    }
+
+    private android.hardware.radio.V1_0.LteSignalStrength getLteSignalStrength_1_0() {
+        android.hardware.radio.V1_0.LteSignalStrength signalStrength =
+                new android.hardware.radio.V1_0.LteSignalStrength();
+        signalStrength.signalStrength = RSSI_ASU;
+        signalStrength.rsrp = -RSRP;
+        signalStrength.rsrq = -RSRQ;
+        signalStrength.rssnr = RSSNR;
+        signalStrength.cqi = CQI;
+        signalStrength.timingAdvance = TIMING_ADVANCE;
+
+        return signalStrength;
+    }
+
+    private android.hardware.radio.V1_4.CellInfo getCellInfo_1_4ForLte() {
+        android.hardware.radio.V1_2.CellInfoLte cellInfo2 =
+                new android.hardware.radio.V1_2.CellInfoLte();
+        cellInfo2.cellIdentityLte = getCellIdentityLte_1_2();
+        cellInfo2.signalStrengthLte = getLteSignalStrength_1_0();
+
+        android.hardware.radio.V1_4.CellConfigLte cellConfig =
+                new android.hardware.radio.V1_4.CellConfigLte();
+        cellConfig.isEndcAvailable = ENDC_AVAILABLE;
+
+        android.hardware.radio.V1_4.CellInfoLte cellInfoLte =
+                new android.hardware.radio.V1_4.CellInfoLte();
+        cellInfoLte.base = cellInfo2;
+        cellInfoLte.cellConfig = cellConfig;
+
+        android.hardware.radio.V1_4.CellInfo cellInfo = new android.hardware.radio.V1_4.CellInfo();
+        cellInfo.isRegistered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.info.lte(cellInfoLte);
+
+        return cellInfo;
+    }
+
     @Test
-    public void testConvertHalCellInfoListForLTE() {
-        android.hardware.radio.V1_0.CellInfoLte lte = new android.hardware.radio.V1_0.CellInfoLte();
-        lte.cellIdentityLte.ci = CI;
-        lte.cellIdentityLte.pci = PCI;
-        lte.cellIdentityLte.tac = TAC;
-        lte.cellIdentityLte.earfcn = EARFCN;
-        lte.cellIdentityLte.mcc = MCC_STR;
-        lte.cellIdentityLte.mnc = MNC_STR;
-        lte.signalStrengthLte.signalStrength = RSSI_ASU;
-        lte.signalStrengthLte.rsrp = -RSRP;
-        lte.signalStrengthLte.rsrq = -RSRQ;
-        lte.signalStrengthLte.rssnr = RSSNR;
-        lte.signalStrengthLte.cqi = CQI;
-        lte.signalStrengthLte.timingAdvance = TIMING_ADVANCE;
-        android.hardware.radio.V1_0.CellInfo record = new android.hardware.radio.V1_0.CellInfo();
-        record.cellInfoType = TYPE_LTE;
-        record.registered = false;
-        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-        record.timeStamp = TIMESTAMP;
-        record.lte.add(lte);
+    public void testConvertHalCellInfoList_1_4ForLte() {
         ArrayList<Object> records = new ArrayList<>();
-        records.add(record);
+        records.add(getCellInfo_1_4ForLte());
 
         ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoLte cellInfoLte = (CellInfoLte) ret.get(0);
-        CellInfoLte expected = new CellInfoLte();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityLte cil = new CellIdentityLte(CI, PCI, TAC, EARFCN, new int[] {},
-                Integer.MAX_VALUE, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
-                Collections.emptyList(), null);
-        CellSignalStrengthLte css = new CellSignalStrengthLte(
-                RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
-        expected.setCellIdentity(cil);
-        expected.setCellSignalStrength(css);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_UNKNOWN);
         cellInfoLte.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        CellIdentityLte cellIdentityLte = new CellIdentityLte(CI, PCI, TAC, EARFCN, new int[] {},
+                BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(),
+                null);
+        CellSignalStrengthLte cellSignalStrengthLte = new CellSignalStrengthLte(
+                RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
+        CellConfigLte cellConfigLte = new CellConfigLte(ENDC_AVAILABLE);
+        CellInfoLte expected = new CellInfoLte(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityLte, cellSignalStrengthLte, cellConfigLte);
         assertEquals(expected, cellInfoLte);
     }
 
+    private android.hardware.radio.V1_5.OptionalCsgInfo getOptionalCsgInfo() {
+        android.hardware.radio.V1_5.ClosedSubscriberGroupInfo closedSubscriberGroupInfo =
+                new android.hardware.radio.V1_5.ClosedSubscriberGroupInfo();
+        closedSubscriberGroupInfo.csgIndication = CSG_INDICATION;
+        closedSubscriberGroupInfo.homeNodebName = HOME_NODEB_NAME;
+        closedSubscriberGroupInfo.csgIdentity = CSG_IDENTITY;
+
+        android.hardware.radio.V1_5.OptionalCsgInfo optionalCsgInfo =
+                new android.hardware.radio.V1_5.OptionalCsgInfo();
+        optionalCsgInfo.csgInfo(closedSubscriberGroupInfo);
+
+        return optionalCsgInfo;
+    }
+
+    private android.hardware.radio.V1_5.CellIdentityLte getCellIdentityLte_1_5() {
+        android.hardware.radio.V1_5.CellIdentityLte cellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityLte();
+        cellIdentity.base = getCellIdentityLte_1_2();
+        cellIdentity.additionalPlmns = new ArrayList<>(Arrays.asList(ADDITIONAL_PLMNS));
+        cellIdentity.optionalCsgInfo = getOptionalCsgInfo();
+        cellIdentity.bands = BANDS;
+
+        return cellIdentity;
+    }
+
     @Test
-    public void testConvertHalCellInfoListForGSM() {
-        android.hardware.radio.V1_0.CellInfoGsm cellinfo =
-                new android.hardware.radio.V1_0.CellInfoGsm();
-        cellinfo.cellIdentityGsm.lac = LAC;
-        cellinfo.cellIdentityGsm.cid = CID;
-        cellinfo.cellIdentityGsm.bsic = BSIC;
-        cellinfo.cellIdentityGsm.arfcn = ARFCN;
-        cellinfo.cellIdentityGsm.mcc = MCC_STR;
-        cellinfo.cellIdentityGsm.mnc = MNC_STR;
-        cellinfo.signalStrengthGsm.signalStrength = RSSI_ASU;
-        cellinfo.signalStrengthGsm.bitErrorRate = BIT_ERROR_RATE;
-        cellinfo.signalStrengthGsm.timingAdvance = TIMING_ADVANCE;
-        android.hardware.radio.V1_0.CellInfo record = new android.hardware.radio.V1_0.CellInfo();
-        record.cellInfoType = TYPE_GSM;
-        record.registered = false;
-        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-        record.timeStamp = TIMESTAMP;
-        record.gsm.add(cellinfo);
+    public void testConvertHalCellInfoList_1_5ForLte() {
+        android.hardware.radio.V1_5.CellInfoLte cellInfoLte =
+                new android.hardware.radio.V1_5.CellInfoLte();
+        cellInfoLte.cellIdentityLte = getCellIdentityLte_1_5();
+        cellInfoLte.signalStrengthLte = getLteSignalStrength_1_0();
+
+        android.hardware.radio.V1_5.CellInfo cellInfo = new android.hardware.radio.V1_5.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.lte(cellInfoLte);
+
         ArrayList<Object> records = new ArrayList<>();
-        records.add(record);
+        records.add(cellInfo);
+
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
+
+        assertEquals(1, ret.size());
+        CellInfoLte cil = (CellInfoLte) ret.get(0);
+        cil.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+        ClosedSubscriberGroupInfo closedSubscriberGroupInfo =
+                new ClosedSubscriberGroupInfo(CSG_INDICATION, HOME_NODEB_NAME, CSG_IDENTITY);
+        CellIdentityLte cellIdentityLte = new CellIdentityLte(CI, PCI, TAC, EARFCN,
+                BANDS.stream().mapToInt(i -> i).toArray(), BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG,
+                ALPHA_SHORT, additionalPlmns, closedSubscriberGroupInfo);
+        CellSignalStrengthLte cellSignalStrengthLte = new CellSignalStrengthLte(
+                RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
+        CellInfoLte expected = new CellInfoLte(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityLte, cellSignalStrengthLte, new CellConfigLte());
+        assertEquals(expected, cil);
+    }
+
+    @Test
+    public void testConvertHalCellInfoList_1_6ForLte() {
+        android.hardware.radio.V1_6.LteSignalStrength signalStrength =
+                new android.hardware.radio.V1_6.LteSignalStrength();
+        signalStrength.base = getLteSignalStrength_1_0();
+        signalStrength.cqiTableIndex = CQI_TABLE_INDEX;
+
+        android.hardware.radio.V1_6.CellInfoLte cellInfoLte =
+                new android.hardware.radio.V1_6.CellInfoLte();
+        cellInfoLte.cellIdentityLte = getCellIdentityLte_1_5();
+        cellInfoLte.signalStrengthLte = signalStrength;
+
+        android.hardware.radio.V1_6.CellInfo cellInfo = new android.hardware.radio.V1_6.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.lte(cellInfoLte);
+
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
+
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
+
+        assertEquals(1, ret.size());
+        CellInfoLte cil = (CellInfoLte) ret.get(0);
+        cil.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+        ClosedSubscriberGroupInfo closedSubscriberGroupInfo =
+                new ClosedSubscriberGroupInfo(CSG_INDICATION, HOME_NODEB_NAME, CSG_IDENTITY);
+        CellIdentityLte cellIdentityLte = new CellIdentityLte(CI, PCI, TAC, EARFCN,
+                BANDS.stream().mapToInt(i -> i).toArray(), BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG,
+                ALPHA_SHORT, additionalPlmns, closedSubscriberGroupInfo);
+        CellSignalStrengthLte cellSignalStrengthLte = new CellSignalStrengthLte(
+                RSSI, RSRP, RSRQ, RSSNR, CQI_TABLE_INDEX, CQI, TIMING_ADVANCE);
+        CellInfoLte expected = new CellInfoLte(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityLte, cellSignalStrengthLte, new CellConfigLte());
+        assertEquals(expected, cil);
+    }
+
+    private android.hardware.radio.V1_2.CellIdentityGsm getCellIdentityGsm_1_2() {
+        android.hardware.radio.V1_0.CellIdentityGsm cellIdentity0 =
+                new android.hardware.radio.V1_0.CellIdentityGsm();
+        cellIdentity0.mcc = MCC_STR;
+        cellIdentity0.mnc = MNC_STR;
+        cellIdentity0.lac = LAC;
+        cellIdentity0.cid = CID;
+        cellIdentity0.arfcn = ARFCN;
+        cellIdentity0.bsic = BSIC;
+
+        android.hardware.radio.V1_2.CellIdentityGsm cellIdentity =
+                new android.hardware.radio.V1_2.CellIdentityGsm();
+        cellIdentity.base = cellIdentity0;
+        cellIdentity.operatorNames = getCellIdentityOperatorNames();
+
+        return cellIdentity;
+    }
+
+    private android.hardware.radio.V1_0.GsmSignalStrength getGsmSignalStrength_1_0() {
+        android.hardware.radio.V1_0.GsmSignalStrength signalStrength =
+                new android.hardware.radio.V1_0.GsmSignalStrength();
+        signalStrength.signalStrength = RSSI_ASU;
+        signalStrength.bitErrorRate = BIT_ERROR_RATE;
+        signalStrength.timingAdvance = TIMING_ADVANCE;
+
+        return signalStrength;
+    }
+
+    @Test
+    public void testConvertHalCellInfoList_1_4ForGsm() {
+        android.hardware.radio.V1_2.CellInfoGsm cellInfoGsm =
+                new android.hardware.radio.V1_2.CellInfoGsm();
+        cellInfoGsm.cellIdentityGsm = getCellIdentityGsm_1_2();
+        cellInfoGsm.signalStrengthGsm = getGsmSignalStrength_1_0();
+
+        android.hardware.radio.V1_4.CellInfo cellInfo = new android.hardware.radio.V1_4.CellInfo();
+        cellInfo.isRegistered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.info.gsm(cellInfoGsm);
+
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
+
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
+
+        assertEquals(1, ret.size());
+        CellInfoGsm cig = (CellInfoGsm) ret.get(0);
+        cig.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        CellIdentityGsm cellIdentityGsm = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, MCC_STR,
+                MNC_STR, ALPHA_LONG, ALPHA_SHORT, Collections.emptyList());
+        CellSignalStrengthGsm cellSignalStrengthGsm = new CellSignalStrengthGsm(
+                RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
+        CellInfoGsm expected = new CellInfoGsm(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityGsm, cellSignalStrengthGsm);
+        assertEquals(expected, cig);
+    }
+
+    private android.hardware.radio.V1_5.CellInfoGsm getCellInfoGsm_1_5() {
+        android.hardware.radio.V1_5.CellIdentityGsm cellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityGsm();
+        cellIdentity.base = getCellIdentityGsm_1_2();
+        cellIdentity.additionalPlmns = new ArrayList<>(Arrays.asList(ADDITIONAL_PLMNS));
+
+        android.hardware.radio.V1_5.CellInfoGsm cellInfo =
+                new android.hardware.radio.V1_5.CellInfoGsm();
+        cellInfo.cellIdentityGsm = cellIdentity;
+        cellInfo.signalStrengthGsm = getGsmSignalStrength_1_0();
+
+        return cellInfo;
+    }
+
+    @Test
+    public void testConvertHalCellInfoList_1_5ForGsm() {
+        android.hardware.radio.V1_5.CellInfo cellInfo = new android.hardware.radio.V1_5.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.gsm(getCellInfoGsm_1_5());
+
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
 
         ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoGsm cellInfoGsm = (CellInfoGsm) ret.get(0);
-        CellInfoGsm expected = new CellInfoGsm();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityGsm ci = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
-                Collections.emptyList());
-        CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
-                RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_UNKNOWN);
         cellInfoGsm.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+        CellIdentityGsm cellIdentityGsm = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, MCC_STR,
+                MNC_STR, ALPHA_LONG, ALPHA_SHORT, additionalPlmns);
+        CellSignalStrengthGsm cellSignalStrengthGsm = new CellSignalStrengthGsm(
+                RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
+        CellInfoGsm expected = new CellInfoGsm(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityGsm, cellSignalStrengthGsm);
         assertEquals(expected, cellInfoGsm);
     }
 
     @Test
-    public void testConvertHalCellInfoListForWcdma() {
-        android.hardware.radio.V1_0.CellInfoWcdma cellinfo =
-                new android.hardware.radio.V1_0.CellInfoWcdma();
-        cellinfo.cellIdentityWcdma.lac = LAC;
-        cellinfo.cellIdentityWcdma.cid = CID;
-        cellinfo.cellIdentityWcdma.psc = PSC;
-        cellinfo.cellIdentityWcdma.uarfcn = UARFCN;
-        cellinfo.cellIdentityWcdma.mcc = MCC_STR;
-        cellinfo.cellIdentityWcdma.mnc = MNC_STR;
-        cellinfo.signalStrengthWcdma.signalStrength = RSSI_ASU;
-        cellinfo.signalStrengthWcdma.bitErrorRate = BIT_ERROR_RATE;
-        android.hardware.radio.V1_0.CellInfo record = new android.hardware.radio.V1_0.CellInfo();
-        record.cellInfoType = TYPE_WCDMA;
-        record.registered = false;
-        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-        record.timeStamp = TIMESTAMP;
-        record.wcdma.add(cellinfo);
+    public void testConvertHalCellInfoList_1_6ForGsm() {
+        android.hardware.radio.V1_6.CellInfo cellInfo = new android.hardware.radio.V1_6.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.gsm(getCellInfoGsm_1_5());
+
         ArrayList<Object> records = new ArrayList<>();
-        records.add(record);
+        records.add(cellInfo);
+
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
+
+        assertEquals(1, ret.size());
+        CellInfoGsm cellInfoGsm = (CellInfoGsm) ret.get(0);
+        cellInfoGsm.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+        CellIdentityGsm cellIdentityGsm = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, MCC_STR,
+                MNC_STR, ALPHA_LONG, ALPHA_SHORT, additionalPlmns);
+        CellSignalStrengthGsm cellSignalStrengthGsm = new CellSignalStrengthGsm(
+                RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
+        CellInfoGsm expected = new CellInfoGsm(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityGsm, cellSignalStrengthGsm);
+        assertEquals(expected, cellInfoGsm);
+    }
+
+    private android.hardware.radio.V1_2.CellIdentityWcdma getCellIdentityWcdma_1_2() {
+        android.hardware.radio.V1_0.CellIdentityWcdma cellIdentity0 =
+                new android.hardware.radio.V1_0.CellIdentityWcdma();
+        cellIdentity0.mcc = MCC_STR;
+        cellIdentity0.mnc = MNC_STR;
+        cellIdentity0.lac = LAC;
+        cellIdentity0.cid = CID;
+        cellIdentity0.psc = PSC;
+        cellIdentity0.uarfcn = UARFCN;
+
+        android.hardware.radio.V1_2.CellIdentityWcdma cellIdentity =
+                new android.hardware.radio.V1_2.CellIdentityWcdma();
+        cellIdentity.base = cellIdentity0;
+        cellIdentity.operatorNames = getCellIdentityOperatorNames();
+
+        return cellIdentity;
+    }
+
+    private android.hardware.radio.V1_2.WcdmaSignalStrength getWcdmaSignalStrength_1_2() {
+        android.hardware.radio.V1_0.WcdmaSignalStrength signalStrength0 =
+                new android.hardware.radio.V1_0.WcdmaSignalStrength();
+        signalStrength0.signalStrength = RSSI_ASU;
+        signalStrength0.bitErrorRate = BIT_ERROR_RATE;
+
+        android.hardware.radio.V1_2.WcdmaSignalStrength signalStrength =
+                new android.hardware.radio.V1_2.WcdmaSignalStrength();
+        signalStrength.base = signalStrength0;
+        signalStrength.rscp = RSCP_ASU;
+        signalStrength.ecno = ECNO_ASU;
+
+        return signalStrength;
+    }
+
+    @Test
+    public void testConvertHalCellInfoList_1_4ForWcdma() {
+        android.hardware.radio.V1_2.CellInfoWcdma cellInfoWcdma =
+                new android.hardware.radio.V1_2.CellInfoWcdma();
+        cellInfoWcdma.cellIdentityWcdma = getCellIdentityWcdma_1_2();
+        cellInfoWcdma.signalStrengthWcdma = getWcdmaSignalStrength_1_2();
+
+        android.hardware.radio.V1_4.CellInfo cellInfo = new android.hardware.radio.V1_4.CellInfo();
+        cellInfo.isRegistered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.info.wcdma(cellInfoWcdma);
+
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
+
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
+
+        assertEquals(1, ret.size());
+        CellInfoWcdma ciw = (CellInfoWcdma) ret.get(0);
+        ciw.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        CellIdentityWcdma cellIdentityWcdma = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR,
+                MNC_STR, ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
+        CellSignalStrengthWcdma cellSignalStrengthWcdma = new CellSignalStrengthWcdma(
+                RSSI, BIT_ERROR_RATE, RSCP, ECNO);
+        CellInfoWcdma expected = new CellInfoWcdma(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityWcdma, cellSignalStrengthWcdma);
+        assertEquals(expected, ciw);
+    }
+
+    private android.hardware.radio.V1_5.CellInfoWcdma getCellInfoWcdma_1_5() {
+        android.hardware.radio.V1_5.CellIdentityWcdma cellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityWcdma();
+        cellIdentity.base = getCellIdentityWcdma_1_2();
+        cellIdentity.additionalPlmns = new ArrayList<>(Arrays.asList(ADDITIONAL_PLMNS));
+        cellIdentity.optionalCsgInfo = getOptionalCsgInfo();
+
+        android.hardware.radio.V1_5.CellInfoWcdma cellInfo =
+                new android.hardware.radio.V1_5.CellInfoWcdma();
+        cellInfo.cellIdentityWcdma = cellIdentity;
+        cellInfo.signalStrengthWcdma = getWcdmaSignalStrength_1_2();
+
+        return cellInfo;
+    }
+
+    @Test
+    public void testConvertHalCellInfoList_1_5ForWcdma() {
+        android.hardware.radio.V1_5.CellInfo cellInfo = new android.hardware.radio.V1_5.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.wcdma(getCellInfoWcdma_1_5());
+
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
 
         ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) ret.get(0);
-        CellInfoWcdma expected = new CellInfoWcdma();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityWcdma ci = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
-                Collections.emptyList(), null);
-        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(
-                RSSI, BIT_ERROR_RATE, Integer.MAX_VALUE, Integer.MAX_VALUE);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_UNKNOWN);
         cellInfoWcdma.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+        ClosedSubscriberGroupInfo closedSubscriberGroupInfo =
+                new ClosedSubscriberGroupInfo(CSG_INDICATION, HOME_NODEB_NAME, CSG_IDENTITY);
+        CellIdentityWcdma cellIdentityWcdma = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR,
+                MNC_STR, ALPHA_LONG, ALPHA_SHORT, additionalPlmns, closedSubscriberGroupInfo);
+        CellSignalStrengthWcdma cellSignalStrengthWcdma = new CellSignalStrengthWcdma(
+                RSSI, BIT_ERROR_RATE, RSCP, ECNO);
+        CellInfoWcdma expected = new CellInfoWcdma(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityWcdma, cellSignalStrengthWcdma);
         assertEquals(expected, cellInfoWcdma);
     }
 
-    private static void initializeCellIdentityTdscdma_1_2(
-            android.hardware.radio.V1_2.CellIdentityTdscdma cid) {
-        cid.base.lac = LAC;
-        cid.base.cid = CID;
-        cid.base.cpid = PSC;
-        cid.base.mcc = MCC_STR;
-        cid.base.mnc = MNC_STR;
-        cid.uarfcn = UARFCN;
-        cid.operatorNames.alphaLong = ALPHA_LONG;
-        cid.operatorNames.alphaShort = ALPHA_SHORT;
+    @Test
+    public void testConvertHalCellInfoList_1_6ForWcdma() {
+        android.hardware.radio.V1_6.CellInfo cellInfo = new android.hardware.radio.V1_6.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.wcdma(getCellInfoWcdma_1_5());
+
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
+
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
+
+        assertEquals(1, ret.size());
+        CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) ret.get(0);
+        cellInfoWcdma.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+        ClosedSubscriberGroupInfo closedSubscriberGroupInfo =
+                new ClosedSubscriberGroupInfo(CSG_INDICATION, HOME_NODEB_NAME, CSG_IDENTITY);
+        CellIdentityWcdma cellIdentityWcdma = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR,
+                MNC_STR, ALPHA_LONG, ALPHA_SHORT, additionalPlmns, closedSubscriberGroupInfo);
+        CellSignalStrengthWcdma cellSignalStrengthWcdma = new CellSignalStrengthWcdma(
+                RSSI, BIT_ERROR_RATE, RSCP, ECNO);
+        CellInfoWcdma expected = new CellInfoWcdma(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityWcdma, cellSignalStrengthWcdma);
+        assertEquals(expected, cellInfoWcdma);
+    }
+
+    private android.hardware.radio.V1_2.CellIdentityTdscdma getCellIdentityTdscdma_1_2() {
+        android.hardware.radio.V1_0.CellIdentityTdscdma cellIdentity0 =
+                new android.hardware.radio.V1_0.CellIdentityTdscdma();
+        cellIdentity0.mcc = MCC_STR;
+        cellIdentity0.mnc = MNC_STR;
+        cellIdentity0.lac = LAC;
+        cellIdentity0.cid = CID;
+        cellIdentity0.cpid = PSC;
+
+        android.hardware.radio.V1_2.CellIdentityTdscdma cellIdentity =
+                new android.hardware.radio.V1_2.CellIdentityTdscdma();
+        cellIdentity.base = cellIdentity0;
+        cellIdentity.uarfcn = UARFCN;
+        cellIdentity.operatorNames = getCellIdentityOperatorNames();
+
+        return cellIdentity;
+    }
+
+    private android.hardware.radio.V1_2.TdscdmaSignalStrength getTdscdmaSignalStrength_1_2() {
+        android.hardware.radio.V1_2.TdscdmaSignalStrength signalStrength =
+                new android.hardware.radio.V1_2.TdscdmaSignalStrength();
+        signalStrength.signalStrength = RSSI_ASU;
+        signalStrength.bitErrorRate = BIT_ERROR_RATE;
+        signalStrength.rscp = RSCP_ASU;
+
+        return signalStrength;
     }
 
     @Test
-    public void testConvertHalCellInfoListForTdscdma() {
-        android.hardware.radio.V1_2.CellInfoTdscdma cellinfo =
+    public void testConvertHalCellInfoList_1_4ForTdscdma() {
+        android.hardware.radio.V1_2.CellInfoTdscdma cellInfoTdscdma =
                 new android.hardware.radio.V1_2.CellInfoTdscdma();
-        initializeCellIdentityTdscdma_1_2(cellinfo.cellIdentityTdscdma);
+        cellInfoTdscdma.cellIdentityTdscdma = getCellIdentityTdscdma_1_2();
+        cellInfoTdscdma.signalStrengthTdscdma = getTdscdmaSignalStrength_1_2();
 
-        cellinfo.signalStrengthTdscdma.signalStrength = RSSI_ASU;
-        cellinfo.signalStrengthTdscdma.bitErrorRate = BIT_ERROR_RATE;
-        cellinfo.signalStrengthTdscdma.rscp = RSCP_ASU;
-        android.hardware.radio.V1_2.CellInfo record = new android.hardware.radio.V1_2.CellInfo();
-        record.cellInfoType = TYPE_TD_SCDMA;
-        record.registered = false;
-        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-        record.timeStamp = TIMESTAMP;
-        record.tdscdma.add(cellinfo);
+        android.hardware.radio.V1_4.CellInfo cellInfo = new android.hardware.radio.V1_4.CellInfo();
+        cellInfo.isRegistered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.info.tdscdma(cellInfoTdscdma);
+
         ArrayList<Object> records = new ArrayList<>();
-        records.add(record);
+        records.add(cellInfo);
+
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
+
+        assertEquals(1, ret.size());
+        CellInfoTdscdma cit = (CellInfoTdscdma) ret.get(0);
+        cit.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        CellIdentityTdscdma cellIdentityTdscdma = new CellIdentityTdscdma(
+                MCC_STR, MNC_STR, LAC, CID, PSC, UARFCN, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
+        CellSignalStrengthTdscdma cellSignalStrengthTdscdma = new CellSignalStrengthTdscdma(
+                RSSI, BIT_ERROR_RATE, RSCP);
+        CellInfoTdscdma expected = new CellInfoTdscdma(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityTdscdma, cellSignalStrengthTdscdma);
+        assertEquals(expected, cit);
+    }
+
+    private android.hardware.radio.V1_5.CellInfoTdscdma getCellInfoTdscdma_1_5() {
+        android.hardware.radio.V1_5.CellIdentityTdscdma cellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityTdscdma();
+        cellIdentity.base = getCellIdentityTdscdma_1_2();
+        cellIdentity.additionalPlmns = new ArrayList<>(Arrays.asList(ADDITIONAL_PLMNS));
+        cellIdentity.optionalCsgInfo = getOptionalCsgInfo();
+
+        android.hardware.radio.V1_5.CellInfoTdscdma cellInfo =
+                new android.hardware.radio.V1_5.CellInfoTdscdma();
+        cellInfo.cellIdentityTdscdma = cellIdentity;
+        cellInfo.signalStrengthTdscdma = getTdscdmaSignalStrength_1_2();
+
+        return cellInfo;
+    }
+
+    @Test
+    public void testConvertHalCellInfoList_1_5ForTdscdma() {
+        android.hardware.radio.V1_5.CellInfo cellInfo = new android.hardware.radio.V1_5.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.tdscdma(getCellInfoTdscdma_1_5());
+
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
 
         ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoTdscdma cellInfoTdscdma = (CellInfoTdscdma) ret.get(0);
-        CellInfoTdscdma expected = new CellInfoTdscdma();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        CellIdentityTdscdma ci = new CellIdentityTdscdma(
-                MCC_STR, MNC_STR, LAC, CID, PSC, UARFCN, ALPHA_LONG, ALPHA_SHORT,
-                Collections.emptyList(), null);
-        CellSignalStrengthTdscdma cs = new CellSignalStrengthTdscdma(
-                RSSI, BIT_ERROR_RATE, RSCP);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
         cellInfoTdscdma.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+        ClosedSubscriberGroupInfo closedSubscriberGroupInfo =
+                new ClosedSubscriberGroupInfo(CSG_INDICATION, HOME_NODEB_NAME, CSG_IDENTITY);
+        CellIdentityTdscdma cellIdentityTdscdma = new CellIdentityTdscdma(
+                MCC_STR, MNC_STR, LAC, CID, PSC, UARFCN, ALPHA_LONG, ALPHA_SHORT,
+                additionalPlmns, closedSubscriberGroupInfo);
+        CellSignalStrengthTdscdma cellSignalStrengthTdscdma = new CellSignalStrengthTdscdma(
+                RSSI, BIT_ERROR_RATE, RSCP);
+        CellInfoTdscdma expected = new CellInfoTdscdma(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityTdscdma, cellSignalStrengthTdscdma);
         assertEquals(expected, cellInfoTdscdma);
     }
 
     @Test
-    public void testConvertHalCellInfoListForCdma() {
-        android.hardware.radio.V1_0.CellInfoCdma cellinfo =
-                new android.hardware.radio.V1_0.CellInfoCdma();
-        cellinfo.cellIdentityCdma.networkId = NETWORK_ID;
-        cellinfo.cellIdentityCdma.systemId = SYSTEM_ID;
-        cellinfo.cellIdentityCdma.baseStationId = BASESTATION_ID;
-        cellinfo.cellIdentityCdma.longitude = LONGITUDE;
-        cellinfo.cellIdentityCdma.latitude = LATITUDE;
-        cellinfo.signalStrengthCdma.dbm = -DBM;
-        cellinfo.signalStrengthCdma.ecio = -ECIO;
-        cellinfo.signalStrengthEvdo.dbm = -DBM;
-        cellinfo.signalStrengthEvdo.ecio = -ECIO;
-        cellinfo.signalStrengthEvdo.signalNoiseRatio = SIGNAL_NOISE_RATIO;
-        android.hardware.radio.V1_0.CellInfo record = new android.hardware.radio.V1_0.CellInfo();
-        record.cellInfoType = TYPE_CDMA;
-        record.registered = false;
-        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-        record.timeStamp = TIMESTAMP;
-        record.cdma.add(cellinfo);
+    public void testConvertHalCellInfoList_1_6ForTdscdma() {
+        android.hardware.radio.V1_6.CellInfo cellInfo = new android.hardware.radio.V1_6.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.tdscdma(getCellInfoTdscdma_1_5());
+
         ArrayList<Object> records = new ArrayList<>();
-        records.add(record);
+        records.add(cellInfo);
+
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
+
+        assertEquals(1, ret.size());
+        CellInfoTdscdma cellInfoTdscdma = (CellInfoTdscdma) ret.get(0);
+        cellInfoTdscdma.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+        ClosedSubscriberGroupInfo closedSubscriberGroupInfo =
+                new ClosedSubscriberGroupInfo(CSG_INDICATION, HOME_NODEB_NAME, CSG_IDENTITY);
+        CellIdentityTdscdma cellIdentityTdscdma = new CellIdentityTdscdma(
+                MCC_STR, MNC_STR, LAC, CID, PSC, UARFCN, ALPHA_LONG, ALPHA_SHORT,
+                additionalPlmns, closedSubscriberGroupInfo);
+        CellSignalStrengthTdscdma cellSignalStrengthTdscdma = new CellSignalStrengthTdscdma(
+                RSSI, BIT_ERROR_RATE, RSCP);
+        CellInfoTdscdma expected = new CellInfoTdscdma(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityTdscdma, cellSignalStrengthTdscdma);
+        assertEquals(expected, cellInfoTdscdma);
+    }
+
+    private android.hardware.radio.V1_2.CellInfoCdma getCellInfoCdma_1_2() {
+        android.hardware.radio.V1_0.CellIdentityCdma cellIdentity0 =
+                new android.hardware.radio.V1_0.CellIdentityCdma();
+        cellIdentity0.networkId = NETWORK_ID;
+        cellIdentity0.systemId = SYSTEM_ID;
+        cellIdentity0.baseStationId = BASESTATION_ID;
+        cellIdentity0.longitude = LONGITUDE;
+        cellIdentity0.latitude = LATITUDE;
+
+        android.hardware.radio.V1_2.CellIdentityCdma cellIdentity =
+                new android.hardware.radio.V1_2.CellIdentityCdma();
+        cellIdentity.base = cellIdentity0;
+        cellIdentity.operatorNames = getCellIdentityOperatorNames();
+
+        android.hardware.radio.V1_0.CdmaSignalStrength cdmaSignalStrength =
+                new android.hardware.radio.V1_0.CdmaSignalStrength();
+        cdmaSignalStrength.dbm = -DBM;
+        cdmaSignalStrength.ecio = -ECIO;
+
+        android.hardware.radio.V1_0.EvdoSignalStrength evdoSignalStrength =
+                new android.hardware.radio.V1_0.EvdoSignalStrength();
+        evdoSignalStrength.dbm = -DBM;
+        evdoSignalStrength.ecio = -ECIO;
+        evdoSignalStrength.signalNoiseRatio = SIGNAL_NOISE_RATIO;
+
+        android.hardware.radio.V1_2.CellInfoCdma cellInfo =
+                new android.hardware.radio.V1_2.CellInfoCdma();
+        cellInfo.cellIdentityCdma = cellIdentity;
+        cellInfo.signalStrengthCdma = cdmaSignalStrength;
+        cellInfo.signalStrengthEvdo = evdoSignalStrength;
+
+        return cellInfo;
+    }
+
+    @Test
+    public void testConvertHalCellInfoList_1_4ForCdma() {
+        android.hardware.radio.V1_4.CellInfo cellInfo = new android.hardware.radio.V1_4.CellInfo();
+        cellInfo.isRegistered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.info.cdma(getCellInfoCdma_1_2());
+
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
 
         ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoCdma cellInfoCdma = (CellInfoCdma) ret.get(0);
-        CellInfoCdma expected = new CellInfoCdma();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityCdma ci = new CellIdentityCdma(
-                NETWORK_ID, SYSTEM_ID, BASESTATION_ID, LONGITUDE, LATITUDE,
-                EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
-        CellSignalStrengthCdma cs = new CellSignalStrengthCdma(
-                DBM, ECIO, DBM, ECIO, SIGNAL_NOISE_RATIO);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_UNKNOWN);
         cellInfoCdma.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        CellIdentityCdma cellIdentityCdma = new CellIdentityCdma(NETWORK_ID, SYSTEM_ID,
+                BASESTATION_ID, LONGITUDE, LATITUDE, ALPHA_LONG, ALPHA_SHORT);
+        CellSignalStrengthCdma cellSignalStrengthCdma = new CellSignalStrengthCdma(
+                DBM, ECIO, DBM, ECIO, SIGNAL_NOISE_RATIO);
+        CellInfoCdma expected = new CellInfoCdma(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityCdma, cellSignalStrengthCdma);
         assertEquals(expected, cellInfoCdma);
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForLTE() {
-        ArrayList<CellInfo> ret = getCellInfoListForLTE(MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+    public void testConvertHalCellInfoList_1_5ForCdma() {
+        android.hardware.radio.V1_5.CellInfo cellInfo = new android.hardware.radio.V1_5.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.cdma(getCellInfoCdma_1_2());
 
-        assertEquals(1, ret.size());
-        CellInfoLte cellInfoLte = (CellInfoLte) ret.get(0);
-        CellInfoLte expected = new CellInfoLte();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityLte cil = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, new int[] {}, BANDWIDTH, MCC_STR, MNC_STR,
-                ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
-        CellSignalStrengthLte css = new CellSignalStrengthLte(
-                RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
-        expected.setCellIdentity(cil);
-        expected.setCellSignalStrength(css);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        cellInfoLte.setTimeStamp(TIMESTAMP); // override the timestamp
-        assertEquals(expected, cellInfoLte);
-    }
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
 
-    @Test
-    public void testConvertHalCellInfoList_1_2_ForLTEWithEmptyOperatorInfo() {
-        ArrayList<CellInfo> ret = getCellInfoListForLTE(
-                MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
-
-        assertEquals(1, ret.size());
-        CellInfoLte cellInfoLte = (CellInfoLte) ret.get(0);
-        CellInfoLte expected = new CellInfoLte();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityLte cil = new CellIdentityLte(CI, PCI, TAC, EARFCN, new int[] {},
-                BANDWIDTH, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
-                Collections.emptyList(), null);
-        CellSignalStrengthLte css = new CellSignalStrengthLte(
-                RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
-        expected.setCellIdentity(cil);
-        expected.setCellSignalStrength(css);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        cellInfoLte.setTimeStamp(TIMESTAMP); // override the timestamp
-        assertEquals(expected, cellInfoLte);
-    }
-
-    @Test
-    public void testConvertHalCellInfoList_1_2ForLTEWithEmptyMccMnc() {
-        // MCC/MNC will be set as INT_MAX if unknown
-        ArrayList<CellInfo> ret = getCellInfoListForLTE(
-                String.valueOf(Integer.MAX_VALUE), String.valueOf(Integer.MAX_VALUE),
-                ALPHA_LONG, ALPHA_SHORT);
-
-        assertEquals(1, ret.size());
-        CellInfoLte cellInfoLte = (CellInfoLte) ret.get(0);
-        CellInfoLte expected = new CellInfoLte();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityLte cil = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, new int[] {}, BANDWIDTH, null, null, ALPHA_LONG,
-                ALPHA_SHORT, Collections.emptyList(), null);
-        CellSignalStrengthLte css = new CellSignalStrengthLte(
-                RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
-        expected.setCellIdentity(cil);
-        expected.setCellSignalStrength(css);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        cellInfoLte.setTimeStamp(TIMESTAMP); // override the timestamp
-        assertEquals(expected, cellInfoLte);
-    }
-
-    @Test
-    public void testConvertHalCellInfoList_1_2ForGSM() {
-        ArrayList<CellInfo> ret = getCellInfoListForGSM(MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
-
-        assertEquals(1, ret.size());
-        CellInfoGsm cellInfoGsm = (CellInfoGsm) ret.get(0);
-        CellInfoGsm expected = new CellInfoGsm();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityGsm ci = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
-                Collections.emptyList());
-        CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
-                RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        cellInfoGsm.setTimeStamp(TIMESTAMP); // override the timestamp
-        assertEquals(expected, cellInfoGsm);
-    }
-
-    @Test
-    public void testConvertHalCellInfoList_1_2ForGSMWithEmptyOperatorInfo() {
-        ArrayList<CellInfo> ret = getCellInfoListForGSM(
-                MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
-
-        assertEquals(1, ret.size());
-        CellInfoGsm cellInfoGsm = (CellInfoGsm) ret.get(0);
-        CellInfoGsm expected = new CellInfoGsm();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityGsm ci = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
-                Collections.emptyList());
-        CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
-                RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        cellInfoGsm.setTimeStamp(TIMESTAMP); // override the timestamp
-        assertEquals(expected, cellInfoGsm);
-    }
-
-    @Test
-    public void testConvertHalCellInfoList_1_2ForGSMWithEmptyMccMnc() {
-        // MCC/MNC will be set as INT_MAX if unknown
-        ArrayList<CellInfo> ret = getCellInfoListForGSM(
-                String.valueOf(Integer.MAX_VALUE), String.valueOf(Integer.MAX_VALUE),
-                ALPHA_LONG, ALPHA_SHORT);
-
-        assertEquals(1, ret.size());
-        CellInfoGsm cellInfoGsm = (CellInfoGsm) ret.get(0);
-        CellInfoGsm expected = new CellInfoGsm();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityGsm ci = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT,
-                Collections.emptyList());
-        CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
-                RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
-        expected.setCellIdentity(ci);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        expected.setCellSignalStrength(cs);
-        cellInfoGsm.setTimeStamp(TIMESTAMP); // override the timestamp
-        assertEquals(expected, cellInfoGsm);
-    }
-
-    @Test
-    public void testConvertHalCellInfoList_1_2ForWcdma() {
-        ArrayList<CellInfo> ret = getCellInfoListForWcdma(
-                MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
-
-        assertEquals(1, ret.size());
-        CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) ret.get(0);
-        CellInfoWcdma expected = new CellInfoWcdma();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityWcdma ci = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
-                Collections.emptyList(), null);
-        CellSignalStrengthWcdma cs =
-                new CellSignalStrengthWcdma(RSSI, BIT_ERROR_RATE, RSCP, ECNO);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        cellInfoWcdma.setTimeStamp(TIMESTAMP); // override the timestamp
-        assertEquals(expected, cellInfoWcdma);
-    }
-
-    @Test
-    public void testConvertHalCellInfoList_1_2ForWcdmaWithEmptyOperatorInfo() {
-        ArrayList<CellInfo> ret = getCellInfoListForWcdma(
-                MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
-
-        assertEquals(1, ret.size());
-        CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) ret.get(0);
-        CellInfoWcdma expected = new CellInfoWcdma();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityWcdma ci = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
-                Collections.emptyList(), null);
-        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(
-                RSSI, BIT_ERROR_RATE, RSCP, ECNO);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        cellInfoWcdma.setTimeStamp(TIMESTAMP); // override the timestamp
-        assertEquals(expected, cellInfoWcdma);
-    }
-
-    @Test
-    public void testConvertHalCellInfoList_1_2ForWcdmaWithEmptyMccMnc() {
-        // MCC/MNC will be set as INT_MAX if unknown
-        ArrayList<CellInfo> ret = getCellInfoListForWcdma(null, null, ALPHA_LONG, ALPHA_SHORT);
-
-        assertEquals(1, ret.size());
-        CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) ret.get(0);
-        CellInfoWcdma expected = new CellInfoWcdma();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityWcdma ci = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT,
-                Collections.emptyList(), null);
-        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(
-                RSSI, BIT_ERROR_RATE, RSCP, ECNO);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
-        cellInfoWcdma.setTimeStamp(TIMESTAMP); // override the timestamp
-        assertEquals(expected, cellInfoWcdma);
-    }
-
-    @Test
-    public void testConvertHalCellInfoList_1_2ForCdma() {
-        ArrayList<CellInfo> ret = getCellInfoListForCdma(ALPHA_LONG, ALPHA_SHORT);
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoCdma cellInfoCdma = (CellInfoCdma) ret.get(0);
-        CellInfoCdma expected = new CellInfoCdma();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityCdma ci = new CellIdentityCdma(
-                NETWORK_ID, SYSTEM_ID, BASESTATION_ID, LONGITUDE, LATITUDE,
-                ALPHA_LONG, ALPHA_SHORT);
-        CellSignalStrengthCdma cs = new CellSignalStrengthCdma(
-                DBM, ECIO, DBM, ECIO, SIGNAL_NOISE_RATIO);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
         cellInfoCdma.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        CellIdentityCdma cellIdentityCdma = new CellIdentityCdma(NETWORK_ID, SYSTEM_ID,
+                BASESTATION_ID, LONGITUDE, LATITUDE, ALPHA_LONG, ALPHA_SHORT);
+        CellSignalStrengthCdma cellSignalStrengthCdma = new CellSignalStrengthCdma(
+                DBM, ECIO, DBM, ECIO, SIGNAL_NOISE_RATIO);
+        CellInfoCdma expected = new CellInfoCdma(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityCdma, cellSignalStrengthCdma);
         assertEquals(expected, cellInfoCdma);
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForCdmaWithEmptyOperatorInfo() {
-        ArrayList<CellInfo> ret = getCellInfoListForCdma(EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
+    public void testConvertHalCellInfoList_1_6ForCdma() {
+        android.hardware.radio.V1_6.CellInfo cellInfo = new android.hardware.radio.V1_6.CellInfo();
+        cellInfo.registered = REGISTERED;
+        cellInfo.connectionStatus = CONNECTION_STATUS;
+        cellInfo.ratSpecificInfo.cdma(getCellInfoCdma_1_2());
+
+        ArrayList<Object> records = new ArrayList<>();
+        records.add(cellInfo);
+
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoCdma cellInfoCdma = (CellInfoCdma) ret.get(0);
-        CellInfoCdma expected = new CellInfoCdma();
-        expected.setRegistered(false);
-        expected.setTimeStamp(TIMESTAMP);
-        CellIdentityCdma ci = new CellIdentityCdma(
-                NETWORK_ID, SYSTEM_ID, BASESTATION_ID, LONGITUDE, LATITUDE,
-                EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
-        CellSignalStrengthCdma cs = new CellSignalStrengthCdma(
-                DBM, ECIO, DBM, ECIO, SIGNAL_NOISE_RATIO);
-        expected.setCellIdentity(ci);
-        expected.setCellSignalStrength(cs);
-        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
         cellInfoCdma.setTimeStamp(TIMESTAMP); // override the timestamp
+
+        CellIdentityCdma cellIdentityCdma = new CellIdentityCdma(NETWORK_ID, SYSTEM_ID,
+                BASESTATION_ID, LONGITUDE, LATITUDE, ALPHA_LONG, ALPHA_SHORT);
+        CellSignalStrengthCdma cellSignalStrengthCdma = new CellSignalStrengthCdma(
+                DBM, ECIO, DBM, ECIO, SIGNAL_NOISE_RATIO);
+        CellInfoCdma expected = new CellInfoCdma(CONNECTION_STATUS, REGISTERED, TIMESTAMP,
+                cellIdentityCdma, cellSignalStrengthCdma);
         assertEquals(expected, cellInfoCdma);
     }
 
@@ -2105,170 +2278,8 @@
         assertEquals(expectedSignalStrength, signalStrengthNr);
     }
 
-    private static android.hardware.radio.V1_5.ClosedSubscriberGroupInfo getHalCsgInfo() {
-        android.hardware.radio.V1_5.ClosedSubscriberGroupInfo csgInfo =
-                new android.hardware.radio.V1_5.ClosedSubscriberGroupInfo();
-
-        csgInfo.csgIndication = CSG_INDICATION;
-        csgInfo.homeNodebName = HOME_NODEB_NAME;
-        csgInfo.csgIdentity = CSG_IDENTITY;
-
-        return csgInfo;
-    }
-
-    private static void initializeCellIdentityLte_1_5(
-            android.hardware.radio.V1_5.CellIdentityLte id,
-            boolean addAdditionalPlmns, boolean addCsgInfo) {
-
-        initializeCellIdentityLte_1_2(id.base);
-
-        if (addAdditionalPlmns) {
-            id.additionalPlmns = new ArrayList<>(
-                    Arrays.asList(ADDITIONAL_PLMNS));
-        }
-
-        if (addCsgInfo) {
-            id.optionalCsgInfo.csgInfo(getHalCsgInfo());
-        }
-    }
-
-    @Test
-    public void testCellIdentityLte_1_5_CsgInfo() {
-        android.hardware.radio.V1_5.CellIdentityLte halCellIdentity =
-                new android.hardware.radio.V1_5.CellIdentityLte();
-        initializeCellIdentityLte_1_5(halCellIdentity, false, true);
-
-        CellIdentityLte cellIdentity = RILUtils.convertHalCellIdentityLte(halCellIdentity);
-
-        assertEquals(CSG_INDICATION,
-                cellIdentity.getClosedSubscriberGroupInfo().getCsgIndicator());
-        assertEquals(HOME_NODEB_NAME,
-                cellIdentity.getClosedSubscriberGroupInfo().getHomeNodebName());
-        assertEquals(CSG_IDENTITY,
-                cellIdentity.getClosedSubscriberGroupInfo().getCsgIdentity());
-    }
-
-    @Test
-    public void testCellIdentityLte_1_5_MultiPlmn() {
-        android.hardware.radio.V1_5.CellIdentityLte halCellIdentity =
-                new android.hardware.radio.V1_5.CellIdentityLte();
-        initializeCellIdentityLte_1_5(halCellIdentity, true, false);
-
-        CellIdentityLte cellIdentity = RILUtils.convertHalCellIdentityLte(halCellIdentity);
-
-        Set<String> additionalPlmns = new HashSet<>();
-        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
-
-        assertEquals(cellIdentity.getAdditionalPlmns(), additionalPlmns);
-    }
-
-    private static void initializeCellIdentityWcdma_1_5(
-            android.hardware.radio.V1_5.CellIdentityWcdma id,
-            boolean addAdditionalPlmns, boolean addCsgInfo) {
-
-        initializeCellIdentityWcdma_1_2(id.base);
-
-        if (addAdditionalPlmns) {
-            id.additionalPlmns = new ArrayList<>(Arrays.asList(ADDITIONAL_PLMNS));
-        }
-
-        if (addCsgInfo) {
-            id.optionalCsgInfo.csgInfo(getHalCsgInfo());
-        }
-    }
-
-    @Test
-    public void testCellIdentityWcdma_1_5_CsgInfo() {
-        android.hardware.radio.V1_5.CellIdentityWcdma halCellIdentity =
-                new android.hardware.radio.V1_5.CellIdentityWcdma();
-        initializeCellIdentityWcdma_1_5(halCellIdentity, false, true);
-
-        CellIdentityWcdma cellIdentity = RILUtils.convertHalCellIdentityWcdma(halCellIdentity);
-
-        assertEquals(CSG_INDICATION,
-                cellIdentity.getClosedSubscriberGroupInfo().getCsgIndicator());
-        assertEquals(HOME_NODEB_NAME,
-                cellIdentity.getClosedSubscriberGroupInfo().getHomeNodebName());
-        assertEquals(CSG_IDENTITY,
-                cellIdentity.getClosedSubscriberGroupInfo().getCsgIdentity());
-    }
-
-    @Test
-    public void testCellIdentityWcdma_1_5_MultiPlmn() {
-        android.hardware.radio.V1_5.CellIdentityWcdma halCellIdentity =
-                new android.hardware.radio.V1_5.CellIdentityWcdma();
-        initializeCellIdentityWcdma_1_5(halCellIdentity, true, false);
-
-        CellIdentityWcdma cellIdentity = RILUtils.convertHalCellIdentityWcdma(halCellIdentity);
-
-        Set<String> additionalPlmns = new HashSet<>();
-        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
-
-        assertEquals(cellIdentity.getAdditionalPlmns(), additionalPlmns);
-    }
-
-    private static void initializeCellIdentityTdscdma_1_5(
-            android.hardware.radio.V1_5.CellIdentityTdscdma id,
-            boolean addAdditionalPlmns, boolean addCsgInfo) {
-
-        initializeCellIdentityTdscdma_1_2(id.base);
-
-        if (addAdditionalPlmns) {
-            id.additionalPlmns = new ArrayList<>(Arrays.asList(ADDITIONAL_PLMNS));
-        }
-
-        if (addCsgInfo) {
-            id.optionalCsgInfo.csgInfo(getHalCsgInfo());
-        }
-    }
-
-    @Test
-    public void testCellIdentityTdscdma_1_5_CsgInfo() {
-        android.hardware.radio.V1_5.CellIdentityTdscdma halCellIdentity =
-                new android.hardware.radio.V1_5.CellIdentityTdscdma();
-        initializeCellIdentityTdscdma_1_5(halCellIdentity, false, true);
-
-        CellIdentityTdscdma cellIdentity = RILUtils.convertHalCellIdentityTdscdma(halCellIdentity);
-
-        assertEquals(CSG_INDICATION,
-                cellIdentity.getClosedSubscriberGroupInfo().getCsgIndicator());
-        assertEquals(HOME_NODEB_NAME,
-                cellIdentity.getClosedSubscriberGroupInfo().getHomeNodebName());
-        assertEquals(CSG_IDENTITY,
-                cellIdentity.getClosedSubscriberGroupInfo().getCsgIdentity());
-    }
-
-    @Test
-    public void testCellIdentityTdscdma_1_5_MultiPlmn() {
-        android.hardware.radio.V1_5.CellIdentityTdscdma halCellIdentity =
-                new android.hardware.radio.V1_5.CellIdentityTdscdma();
-        initializeCellIdentityTdscdma_1_5(halCellIdentity, true, false);
-
-        CellIdentityTdscdma cellIdentity = RILUtils.convertHalCellIdentityTdscdma(halCellIdentity);
-
-        Set<String> additionalPlmns = new HashSet<>();
-        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
-
-        assertEquals(cellIdentity.getAdditionalPlmns(), additionalPlmns);
-    }
-
     @Test
     public void testConvertDataCallResult() {
-        // Test V1.0 SetupDataCallResult
-        android.hardware.radio.V1_0.SetupDataCallResult result10 =
-                new android.hardware.radio.V1_0.SetupDataCallResult();
-        result10.status = android.hardware.radio.V1_0.DataCallFailCause.NONE;
-        result10.suggestedRetryTime = -1;
-        result10.cid = 0;
-        result10.active = 2;
-        result10.type = "IPV4V6";
-        result10.ifname = "ifname";
-        result10.addresses = "10.0.2.15 2607:fb90:a620:651d:eabe:f8da:c107:44be/64";
-        result10.dnses = "10.0.2.3 fd00:976a::9";
-        result10.gateways = "10.0.2.15 fe80::2";
-        result10.pcscf = "fd00:976a:c206:20::6   fd00:976a:c206:20::9    fd00:976a:c202:1d::9";
-        result10.mtu = 1500;
-
         DataCallResponse response = new DataCallResponse.Builder()
                 .setCause(0)
                 .setRetryDurationMillis(-1L)
@@ -2294,8 +2305,6 @@
                 .setTrafficDescriptors(new ArrayList<>())
                 .build();
 
-        assertEquals(response, RILUtils.convertHalDataCallResult(result10));
-
         // Test V1.4 SetupDataCallResult
         android.hardware.radio.V1_4.SetupDataCallResult result14 =
                 new android.hardware.radio.V1_4.SetupDataCallResult();
@@ -2532,11 +2541,7 @@
         ArrayList<Object> records = new ArrayList<>();
 
         for (int i = 0; i < 5 /* arbitrary */; i++) {
-            android.hardware.radio.V1_4.CellInfo record =
-                    new android.hardware.radio.V1_4.CellInfo();
-            record.info = new android.hardware.radio.V1_4.CellInfo.Info();
-            record.info.lte(new android.hardware.radio.V1_4.CellInfoLte());
-            initializeCellInfoLte_1_2(record.info.lte().base);
+            android.hardware.radio.V1_4.CellInfo record = getCellInfo_1_4ForLte();
             record.info.lte().base.cellIdentityLte.base.ci += i; // make them marginally unique
 
             records.add(record);
@@ -2552,181 +2557,6 @@
     }
 
     @Test
-    public void testCellInfoTimestamp_1_2() {
-        ArrayList<Object> records = new ArrayList<>();
-
-        for (int i = 0; i < 5 /* arbitrary */; i++) {
-            android.hardware.radio.V1_2.CellInfo record =
-                    new android.hardware.radio.V1_2.CellInfo();
-            record.cellInfoType = TYPE_LTE;
-            record.timeStamp = Long.MAX_VALUE;
-            record.registered = false;
-            record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-            record.lte.add(new android.hardware.radio.V1_2.CellInfoLte());
-            initializeCellInfoLte_1_2(record.lte.get(0));
-            record.lte.get(0).cellIdentityLte.base.ci += i; // make them marginally unique
-
-            records.add(record);
-        }
-        List<CellInfo> cil = RILUtils.convertHalCellInfoList(records);
-
-        // Check that all timestamps are set to a valid number and are equal
-        final long ts = cil.get(0).getTimeStamp();
-        for (CellInfo ci : cil) {
-            assertTrue(ci.getTimeStamp() > 0 && ci.getTimeStamp() != Long.MAX_VALUE);
-            assertEquals(ci.getTimeStamp(), ts);
-        }
-    }
-
-    private static void initializeCellIdentityLte_1_2(
-            android.hardware.radio.V1_2.CellIdentityLte id) {
-        // 1.0 fields
-        id.base.mcc = MCC_STR;
-        id.base.mnc = MNC_STR;
-        id.base.ci = CI;
-        id.base.pci = PCI;
-        id.base.tac = TAC;
-        id.base.earfcn = EARFCN;
-
-        // 1.2 fields
-        id.bandwidth = BANDWIDTH;
-        id.operatorNames.alphaLong = ALPHA_LONG;
-        id.operatorNames.alphaShort = ALPHA_SHORT;
-    }
-
-    private static void initializeCellInfoLte_1_2(android.hardware.radio.V1_2.CellInfoLte lte) {
-        initializeCellIdentityLte_1_2(lte.cellIdentityLte);
-
-        lte.signalStrengthLte.signalStrength = RSSI_ASU;
-        lte.signalStrengthLte.rsrp = -RSRP;
-        lte.signalStrengthLte.rsrq = -RSRQ;
-        lte.signalStrengthLte.rssnr = RSSNR;
-        lte.signalStrengthLte.cqi = CQI;
-        lte.signalStrengthLte.timingAdvance = TIMING_ADVANCE;
-    }
-
-    private ArrayList<CellInfo> getCellInfoListForLTE(
-            String mcc, String mnc, String alphaLong, String alphaShort) {
-        android.hardware.radio.V1_2.CellInfoLte lte = new android.hardware.radio.V1_2.CellInfoLte();
-
-        initializeCellInfoLte_1_2(lte);
-        // Override the defaults for test-specific purposes
-        lte.cellIdentityLte.operatorNames.alphaLong = alphaLong;
-        lte.cellIdentityLte.operatorNames.alphaShort = alphaShort;
-        lte.cellIdentityLte.base.mcc = mcc;
-        lte.cellIdentityLte.base.mnc = mnc;
-
-        android.hardware.radio.V1_2.CellInfo record = new android.hardware.radio.V1_2.CellInfo();
-        record.cellInfoType = TYPE_LTE;
-        record.registered = false;
-        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-        record.timeStamp = TIMESTAMP;
-        record.lte.add(lte);
-        record.connectionStatus = 0;
-        ArrayList<Object> records = new ArrayList<>();
-        records.add(record);
-        return RILUtils.convertHalCellInfoList(records);
-    }
-
-    private ArrayList<CellInfo> getCellInfoListForGSM(
-            String mcc, String mnc, String alphaLong, String alphaShort) {
-        android.hardware.radio.V1_2.CellInfoGsm cellinfo =
-                new android.hardware.radio.V1_2.CellInfoGsm();
-        cellinfo.cellIdentityGsm.base.lac = LAC;
-        cellinfo.cellIdentityGsm.base.cid = CID;
-        cellinfo.cellIdentityGsm.base.bsic = BSIC;
-        cellinfo.cellIdentityGsm.base.arfcn = ARFCN;
-        cellinfo.cellIdentityGsm.base.mcc = mcc;
-        cellinfo.cellIdentityGsm.base.mnc = mnc;
-        cellinfo.cellIdentityGsm.operatorNames.alphaLong = alphaLong;
-        cellinfo.cellIdentityGsm.operatorNames.alphaShort = alphaShort;
-        cellinfo.signalStrengthGsm.signalStrength = RSSI_ASU;
-        cellinfo.signalStrengthGsm.bitErrorRate = BIT_ERROR_RATE;
-        cellinfo.signalStrengthGsm.timingAdvance = TIMING_ADVANCE;
-        android.hardware.radio.V1_2.CellInfo record = new android.hardware.radio.V1_2.CellInfo();
-        record.cellInfoType = TYPE_GSM;
-        record.registered = false;
-        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-        record.timeStamp = TIMESTAMP;
-        record.gsm.add(cellinfo);
-        record.connectionStatus = 0;
-        ArrayList<Object> records = new ArrayList<>();
-        records.add(record);
-
-        return RILUtils.convertHalCellInfoList(records);
-    }
-
-    private static void initializeCellIdentityWcdma_1_2(
-            android.hardware.radio.V1_2.CellIdentityWcdma cid) {
-        initializeCellIdentityWcdma_1_2(cid, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
-    }
-
-    private static void initializeCellIdentityWcdma_1_2(
-            android.hardware.radio.V1_2.CellIdentityWcdma cid,
-                String mcc, String mnc, String alphaLong, String alphaShort) {
-        cid.base.lac = LAC;
-        cid.base.cid = CID;
-        cid.base.psc = PSC;
-        cid.base.uarfcn = UARFCN;
-        cid.base.mcc = mcc;
-        cid.base.mnc = mnc;
-        cid.operatorNames.alphaLong = alphaLong;
-        cid.operatorNames.alphaShort = alphaShort;
-    }
-
-    private ArrayList<CellInfo> getCellInfoListForWcdma(
-            String mcc, String mnc, String alphaLong, String alphaShort) {
-        android.hardware.radio.V1_2.CellInfoWcdma cellinfo =
-                new android.hardware.radio.V1_2.CellInfoWcdma();
-        initializeCellIdentityWcdma_1_2(
-                cellinfo.cellIdentityWcdma, mcc, mnc, alphaLong, alphaShort);
-
-        cellinfo.signalStrengthWcdma.base.signalStrength = RSSI_ASU;
-        cellinfo.signalStrengthWcdma.base.bitErrorRate = BIT_ERROR_RATE;
-        cellinfo.signalStrengthWcdma.rscp = RSCP_ASU;
-        cellinfo.signalStrengthWcdma.ecno = ECNO_ASU;
-        android.hardware.radio.V1_2.CellInfo record = new android.hardware.radio.V1_2.CellInfo();
-        record.cellInfoType = TYPE_WCDMA;
-        record.registered = false;
-        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-        record.timeStamp = TIMESTAMP;
-        record.wcdma.add(cellinfo);
-        record.connectionStatus = 0;
-        ArrayList<Object> records = new ArrayList<>();
-        records.add(record);
-
-        return RILUtils.convertHalCellInfoList(records);
-    }
-
-    private ArrayList<CellInfo> getCellInfoListForCdma(String alphaLong, String alphaShort) {
-        android.hardware.radio.V1_2.CellInfoCdma cellinfo =
-                new android.hardware.radio.V1_2.CellInfoCdma();
-        cellinfo.cellIdentityCdma.base.networkId = NETWORK_ID;
-        cellinfo.cellIdentityCdma.base.systemId = SYSTEM_ID;
-        cellinfo.cellIdentityCdma.base.baseStationId = BASESTATION_ID;
-        cellinfo.cellIdentityCdma.base.longitude = LONGITUDE;
-        cellinfo.cellIdentityCdma.base.latitude = LATITUDE;
-        cellinfo.cellIdentityCdma.operatorNames.alphaLong = alphaLong;
-        cellinfo.cellIdentityCdma.operatorNames.alphaShort = alphaShort;
-        cellinfo.signalStrengthCdma.dbm = -DBM;
-        cellinfo.signalStrengthCdma.ecio = -ECIO;
-        cellinfo.signalStrengthEvdo.dbm = -DBM;
-        cellinfo.signalStrengthEvdo.ecio = -ECIO;
-        cellinfo.signalStrengthEvdo.signalNoiseRatio = SIGNAL_NOISE_RATIO;
-        android.hardware.radio.V1_2.CellInfo record = new android.hardware.radio.V1_2.CellInfo();
-        record.cellInfoType = TYPE_CDMA;
-        record.registered = false;
-        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
-        record.timeStamp = TIMESTAMP;
-        record.cdma.add(cellinfo);
-        record.connectionStatus = 0;
-        ArrayList<Object> records = new ArrayList<>();
-        records.add(record);
-
-        return RILUtils.convertHalCellInfoList(records);
-    }
-
-    @Test
     public void testSetupDataCall() throws Exception {
         ApnSetting apn = new ApnSetting.Builder()
                 .setId(1234)
@@ -2750,13 +2580,12 @@
                 .setPreferred(false)
                 .build();
 
-        mRILUnderTest.setupDataCall(AccessNetworkConstants.AccessNetworkType.EUTRAN, dp, false,
-                false, 0, null, DataCallResponse.PDU_SESSION_ID_NOT_SET, null, null, true,
-                obtainMessage());
+        mRILUnderTest.setupDataCall(AccessNetworkConstants.AccessNetworkType.EUTRAN, dp, false, 0,
+                null, DataCallResponse.PDU_SESSION_ID_NOT_SET, null, null, true, obtainMessage());
         ArgumentCaptor<DataProfile> dpiCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mDataProxy).setupDataCall(mSerialNumberCaptor.capture(),
-                anyInt(), eq(AccessNetworkConstants.AccessNetworkType.EUTRAN), dpiCaptor.capture(),
-                eq(false), eq(false), anyInt(), any(), anyInt(), any(), any(), eq(true));
+                eq(AccessNetworkConstants.AccessNetworkType.EUTRAN), dpiCaptor.capture(),
+                eq(false), anyInt(), any(), anyInt(), any(), any(), eq(true));
         verifyRILResponse(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_SETUP_DATA_CALL);
         DataProfile dpi = dpiCaptor.getValue();
@@ -2775,46 +2604,6 @@
     }
 
     @Test
-    public void testFixupSignalStrength10() {
-        final int gsmWcdmaRssiDbm = -65;
-
-        // Test the positive case where rat=UMTS and SignalStrength=GSM
-        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS)
-                .when(mServiceState).getRilVoiceRadioTechnology();
-
-        SignalStrength gsmSignalStrength = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(gsmWcdmaRssiDbm, 1, CellInfo.UNAVAILABLE),
-                new CellSignalStrengthWcdma(), new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(), new CellSignalStrengthNr());
-        SignalStrength result = mRILUnderTest.fixupSignalStrength10(gsmSignalStrength);
-
-        assertTrue(result.getCellSignalStrengths(CellSignalStrengthGsm.class).isEmpty());
-        assertFalse(result.getCellSignalStrengths(CellSignalStrengthWcdma.class).isEmpty());
-
-        // Even though the dBm values are equal, the above checks ensure that the value has
-        // been migrated to WCDMA (with no change in the top-level getDbm() result).
-        assertEquals(result.getDbm(), gsmSignalStrength.getDbm());
-
-        // Test the no-op case where rat=GSM and SignalStrength=GSM
-        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_GSM)
-                .when(mServiceState).getRilVoiceRadioTechnology();
-        result = mRILUnderTest.fixupSignalStrength10(gsmSignalStrength);
-        assertEquals(result, gsmSignalStrength);
-
-        // Check that non-GSM non-WCDMA signal strengths are also passed through.
-        SignalStrength lteSignalStrength = new SignalStrength(
-                new CellSignalStrengthCdma(), new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(), new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(CellInfo.UNAVAILABLE,
-                        -120, -10, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
-                        CellInfo.UNAVAILABLE), new CellSignalStrengthNr());
-        SignalStrength lteResult = mRILUnderTest.fixupSignalStrength10(lteSignalStrength);
-
-        assertEquals(lteResult, lteSignalStrength);
-    }
-
-    @Test
     public void testCreateCarrierRestrictionList() {
         ArrayList<CarrierIdentifier> carriers = new ArrayList<>();
         carriers.add(new CarrierIdentifier("110", "120", null, null, null, null));
@@ -2857,7 +2646,7 @@
 
         ArrayList<Carrier> result = RILUtils.convertToHalCarrierRestrictionList(carriers);
 
-        assertTrue(result.equals(expected));
+        assertEquals(result, expected);
     }
 
     @Test
@@ -2897,34 +2686,34 @@
     @Test
     public void testAreUiccApplicationsEnabled_nullRadioProxy() throws Exception {
         // Not supported on Radio 1.0.
-        doReturn(null).when(mRILUnderTest).getRadioProxy(any());
+        doReturn(null).when(mRILUnderTest).getRadioProxy();
         Message message = obtainMessage();
         mRILUnderTest.areUiccApplicationsEnabled(message);
         processAllMessages();
         verify(mSimProxy, never()).areUiccApplicationsEnabled(mSerialNumberCaptor.capture());
         // Sending message is handled by getRadioProxy when proxy is null.
         // areUiccApplicationsEnabled shouldn't explicitly send another callback.
-        assertEquals(null, message.obj);
+        assertNull(message.obj);
     }
 
     @Test
-    public void testSetGetCompatVersion() throws Exception {
+    public void testSetGetCompatVersion() {
         final int testRequest = RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT;
 
         // getCompactVersion should return null before first setting
         assertNull(mRILUnderTest.getCompatVersion(testRequest));
 
         // first time setting any valid HalVersion will success
+        mRILUnderTest.setCompatVersion(testRequest, RIL.RADIO_HAL_VERSION_1_5);
+        assertEquals(RIL.RADIO_HAL_VERSION_1_5, mRILUnderTest.getCompatVersion(testRequest));
+
+        // try to set a lower HalVersion will success
         mRILUnderTest.setCompatVersion(testRequest, RIL.RADIO_HAL_VERSION_1_4);
         assertEquals(RIL.RADIO_HAL_VERSION_1_4, mRILUnderTest.getCompatVersion(testRequest));
 
-        // try to set a lower HalVersion will success
-        mRILUnderTest.setCompatVersion(testRequest, RIL.RADIO_HAL_VERSION_1_3);
-        assertEquals(RIL.RADIO_HAL_VERSION_1_3, mRILUnderTest.getCompatVersion(testRequest));
-
         // try to set a greater HalVersion will not success
-        mRILUnderTest.setCompatVersion(testRequest, RIL.RADIO_HAL_VERSION_1_5);
-        assertEquals(RIL.RADIO_HAL_VERSION_1_3, mRILUnderTest.getCompatVersion(testRequest));
+        mRILUnderTest.setCompatVersion(testRequest, RIL.RADIO_HAL_VERSION_1_6);
+        assertEquals(RIL.RADIO_HAL_VERSION_1_4, mRILUnderTest.getCompatVersion(testRequest));
     }
 
     @FlakyTest
@@ -2963,8 +2752,61 @@
         Message message = obtainMessage();
         mRILUnderTest.getImei(message);
         AsyncResult ar = (AsyncResult) message.obj;
-        Assert.assertEquals(null, ar.result);
+        Assert.assertNull(ar.result);
         Assert.assertNotNull(ar.exception.getMessage());
         Assert.assertEquals("REQUEST_NOT_SUPPORTED", ar.exception.getMessage());
     }
+
+    @Test
+    public void testRadioServiceInvokeHelper() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        HandlerThread handlerThread = new HandlerThread("testRilServiceInvokeHelper");
+        handlerThread.start();
+        Handler handler = new Handler(handlerThread.getLooper()) {
+            public void handleMessage(Message msg) {
+                AsyncResult ar = (AsyncResult) msg.obj;
+                if (ar != null && ar.exception instanceof CommandException) {
+                    CommandException.Error err =
+                            ((CommandException) (ar.exception)).getCommandError();
+                    if (err == CommandException.Error.SYSTEM_ERR) {
+                        latch.countDown();
+                    }
+                }
+            }
+        };
+
+        // RuntimeException
+        doThrow(new RuntimeException()).when(mDataProxy).getDataCallList(anyInt());
+        mRILUnderTest.getDataCallList(handler.obtainMessage());
+        assertTrue(latch.await(3, TimeUnit.SECONDS));
+
+        // RemoteException
+        doThrow(new RemoteException()).when(mDataProxy).getDataCallList(anyInt());
+        mRILUnderTest.getDataCallList(handler.obtainMessage());
+        assertEquals(mRILUnderTest.getRadioState(), TelephonyManager.RADIO_POWER_UNAVAILABLE);
+    }
+
+
+    @Test
+    public void testRadioServiceNotAvailable() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        HandlerThread handlerThread = new HandlerThread("testRadioServiceNotAvailable");
+        handlerThread.start();
+        Handler handler = new Handler(handlerThread.getLooper()) {
+            public void handleMessage(Message msg) {
+                AsyncResult ar = (AsyncResult) msg.obj;
+                if (ar != null && ar.exception instanceof CommandException) {
+                    CommandException.Error err =
+                            ((CommandException) (ar.exception)).getCommandError();
+                    if (err == CommandException.Error.RADIO_NOT_AVAILABLE) {
+                        latch.countDown();
+                    }
+                }
+            }
+        };
+
+        when(mDataProxy.isEmpty()).thenReturn(true);
+        mRILUnderTest.getDataCallList(handler.obtainMessage());
+        assertTrue(latch.await(3, TimeUnit.SECONDS));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RadioAccessFamilyTest.java b/tests/telephonytests/src/com/android/internal/telephony/RadioAccessFamilyTest.java
index 97d4a06..722fb96 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RadioAccessFamilyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RadioAccessFamilyTest.java
@@ -21,9 +21,10 @@
 
 import android.telephony.RadioAccessFamily;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
index 7efb886..4c42e2e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
@@ -28,9 +28,10 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
 
+import androidx.test.filters.SmallTest;
+
 import junit.framework.TestCase;
 
 import java.util.ArrayList;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index 5f592d1..1a6557b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
@@ -87,17 +88,18 @@
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.gsm.GsmCellLocation;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 import android.util.Pair;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.data.AccessNetworksManager;
 import com.android.internal.telephony.data.DataNetworkController;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
 import com.android.internal.telephony.metrics.ServiceStateStats;
 import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -144,6 +146,7 @@
     private ServiceStateTrackerTestHandler mSSTTestHandler;
     private PersistableBundle mBundle;
     private SatelliteController mSatelliteController;
+    private EmergencyStateTracker mEmergencyStateTracker;
 
     private static final int EVENT_REGISTERED_TO_NETWORK = 1;
     private static final int EVENT_SUBSCRIPTION_INFO_READY = 2;
@@ -200,7 +203,7 @@
                     listenerArgumentCaptor =
                             ArgumentCaptor.forClass(
                                     CarrierConfigManager.CarrierConfigChangeListener.class);
-            sst = new ServiceStateTracker(mPhone, mSimulatedCommands);
+            sst = new ServiceStateTracker(mPhone, mSimulatedCommands, mFeatureFlags);
             verify(mCarrierConfigManager, atLeast(3)).registerCarrierConfigChangeListener(any(),
                     listenerArgumentCaptor.capture());
             mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(2);
@@ -257,7 +260,8 @@
         mSatelliteController = Mockito.mock(SatelliteController.class);
         replaceInstance(SatelliteController.class, "sInstance", null,
                 mSatelliteController);
-        doReturn(new ArrayList<>()).when(mSatelliteController).getSatellitePlmnList();
+        doReturn(new ArrayList<>()).when(mSatelliteController).getSatellitePlmnsForCarrier(
+                anyInt());
 
         mContextFixture.putResource(R.string.kg_text_message_separator, " \u2014 ");
 
@@ -268,6 +272,7 @@
                 "com.android.phone");
         mContextFixture.putResource(R.string.config_wlan_network_service_package,
                 "com.xyz.iwlan.networkservice");
+
         doReturn(mIwlanNetworkServiceStub).when(mIwlanNetworkServiceStub).asBinder();
         addNetworkService();
 
@@ -302,6 +307,10 @@
         doReturn(true).when(mPackageManager)
                 .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA);
 
+        // Set cellular radio on after boot by default
+        mContextFixture.putBooleanResource(
+                R.bool.config_enable_cellular_on_boot_default, true);
+
         mSSTTestHandler = new ServiceStateTrackerTestHandler(getClass().getSimpleName());
         mSSTTestHandler.start();
         waitUntilReady();
@@ -327,6 +336,10 @@
         mContextFixture.putIntResource(
                 com.android.internal.R.integer.config_delay_for_ims_dereg_millis, 0);
 
+        // Set 1 as default behavior for enable_cellular_on_boot
+        mContextFixture.putBooleanResource(
+                com.android.internal.R.bool.config_enable_cellular_on_boot_default, true);
+
         mBundle.putBoolean(
                 CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL, true);
         mBundle.putInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT, 0);
@@ -380,6 +393,9 @@
         sendCarrierConfigUpdate(PHONE_ID);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
+        mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
+        replaceInstance(EmergencyStateTracker.class, "INSTANCE", null, mEmergencyStateTracker);
+
         logd("ServiceStateTrackerTest -Setup!");
     }
 
@@ -470,6 +486,66 @@
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         verify(mDataNetworkController, times(1)).unregisterDataNetworkControllerCallback(any());
         assertEquals(TelephonyManager.RADIO_POWER_OFF, mSimulatedCommands.getRadioState());
+        verify(mEmergencyStateTracker, never()).onCellularRadioPowerOffRequested();
+    }
+
+    @Test
+    public void testSetRadioPowerExitEmergencyMode() throws Exception {
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+
+        // Set up DSDS environment
+        GsmCdmaPhone phone2 = Mockito.mock(GsmCdmaPhone.class);
+        DataNetworkController dataNetworkController_phone2 =
+                Mockito.mock(DataNetworkController.class);
+        mPhones = new Phone[] {mPhone, phone2};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        doReturn(dataNetworkController_phone2).when(phone2).getDataNetworkController();
+        doReturn(mSST).when(phone2).getServiceStateTracker();
+        doReturn(false).when(mDataNetworkController).areAllDataDisconnected();
+        doReturn(false).when(dataNetworkController_phone2).areAllDataDisconnected();
+        doReturn(1).when(mPhone).getSubId();
+        doReturn(2).when(phone2).getSubId();
+
+        // Start with radio on
+        sst.setRadioPower(true);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
+
+        // Turn on APM
+        sst.setRadioPower(false);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
+
+        // Verify checking emergency mode
+        verify(mEmergencyStateTracker).onCellularRadioPowerOffRequested();
+
+        // Verify that both subs are waiting for all data disconnected
+        verify(mDataNetworkController).tearDownAllDataNetworks(
+                eq(3 /* TEAR_DOWN_REASON_AIRPLANE_MODE_ON */));
+        verify(dataNetworkController_phone2, never()).tearDownAllDataNetworks(anyInt());
+        ArgumentCaptor<DataNetworkController.DataNetworkControllerCallback> callback1 =
+                ArgumentCaptor.forClass(DataNetworkController.DataNetworkControllerCallback.class);
+        ArgumentCaptor<DataNetworkController.DataNetworkControllerCallback> callback2 =
+                ArgumentCaptor.forClass(DataNetworkController.DataNetworkControllerCallback.class);
+        verify(mDataNetworkController, times(1)).registerDataNetworkControllerCallback(
+                callback1.capture());
+        verify(dataNetworkController_phone2, times(1)).registerDataNetworkControllerCallback(
+                callback2.capture());
+
+        // Data disconnected on sub 2, still waiting for data disconnected on sub 1
+        doReturn(true).when(dataNetworkController_phone2).areAllDataDisconnected();
+        callback2.getValue().onAnyDataNetworkExistingChanged(false);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
+        verify(dataNetworkController_phone2, times(1)).unregisterDataNetworkControllerCallback(
+                any());
+
+        // Data disconnected on sub 1, radio should power off now
+        doReturn(true).when(mDataNetworkController).areAllDataDisconnected();
+        callback1.getValue().onAnyDataNetworkExistingChanged(false);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        verify(mDataNetworkController, times(1)).unregisterDataNetworkControllerCallback(any());
+        assertEquals(TelephonyManager.RADIO_POWER_OFF, mSimulatedCommands.getRadioState());
     }
 
     @Test
@@ -511,18 +587,22 @@
     }
 
     private void testSetRadioPowerForReason(int reason) {
+        clearInvocations(mSatelliteController);
         // Radio does not turn on if off for other reason and not emergency call.
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
         assertTrue(sst.getRadioPowerOffReasons().isEmpty());
         sst.setRadioPowerForReason(false, false, false, false, reason);
         assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
+        verify(mSatelliteController).onCellularRadioPowerOffRequested();
+        clearInvocations(mSatelliteController);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
         sst.setRadioPowerForReason(true, false, false, false,
                 TelephonyManager.RADIO_POWER_REASON_USER);
         assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
+        verify(mSatelliteController, never()).onCellularRadioPowerOffRequested();
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
 
@@ -530,6 +610,7 @@
         // had been turned off for.
         sst.setRadioPowerForReason(true, false, false, false, reason);
         assertTrue(sst.getRadioPowerOffReasons().isEmpty());
+        verify(mSatelliteController, never()).onCellularRadioPowerOffRequested();
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
 
@@ -2050,6 +2131,79 @@
     }
 
     @Test
+    public void testPollStateExceptionRadioPowerOn() {
+        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
+        assertEquals(ServiceState.STATE_IN_SERVICE, sst.getServiceState().getState());
+        assertEquals(ServiceState.STATE_IN_SERVICE,
+                sst.getServiceState().getDataRegistrationState());
+
+        sst.mPollingContext[0] = 1;
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_OPERATOR,
+                new AsyncResult(sst.mPollingContext, null,
+                        new CommandException(CommandException.Error.RADIO_NOT_AVAILABLE))));
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+        assertEquals(ServiceState.STATE_IN_SERVICE, sst.getServiceState().getState());
+        assertEquals(ServiceState.STATE_IN_SERVICE,
+                sst.getServiceState().getDataRegistrationState());
+        assertEquals(0, sst.mPollingContext[0]);
+    }
+
+    @Test
+    public void testPollStateExceptionRadioPowerOff() {
+        // Turn off radio first.
+        sst.setRadioPower(false);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(TelephonyManager.RADIO_POWER_OFF, mSimulatedCommands.getRadioState());
+        assertEquals(ServiceState.STATE_POWER_OFF, sst.getServiceState().getState());
+        assertEquals(ServiceState.STATE_POWER_OFF,
+                sst.getServiceState().getDataRegistrationState());
+        // Override service state
+        sst.getServiceState().setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        sst.getServiceState().setDataRegState(ServiceState.STATE_IN_SERVICE);
+
+        sst.mPollingContext[0] = 1;
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_OPERATOR,
+                new AsyncResult(sst.mPollingContext, null,
+                        new CommandException(CommandException.Error.RADIO_NOT_AVAILABLE))));
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+        assertEquals(ServiceState.STATE_POWER_OFF, sst.getServiceState().getVoiceRegState());
+        assertEquals(ServiceState.STATE_POWER_OFF, sst.getServiceState().getDataRegState());
+        assertEquals(1, sst.mPollingContext[0]);
+    }
+
+    @Test
+    public void testPollStateExceptionRadioPowerOffOnIwlan() {
+        // Turn off radio first.
+        sst.setRadioPower(false);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(TelephonyManager.RADIO_POWER_OFF, mSimulatedCommands.getRadioState());
+        assertEquals(ServiceState.STATE_POWER_OFF, sst.getServiceState().getState());
+        assertEquals(ServiceState.STATE_POWER_OFF,
+                sst.getServiceState().getDataRegistrationState());
+        // Override service state
+        sst.getServiceState().setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        sst.getServiceState().setDataRegState(ServiceState.STATE_IN_SERVICE);
+        // Override to IWLAN
+        sst.mSS.setRilDataRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN);
+
+        sst.mPollingContext[0] = 1;
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_OPERATOR,
+                new AsyncResult(sst.mPollingContext, null,
+                        new CommandException(CommandException.Error.RADIO_NOT_AVAILABLE))));
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+        assertNull(null, sst.getServiceState().getOperatorAlpha());
+        assertEquals(ServiceState.STATE_POWER_OFF, sst.getServiceState().getVoiceRegState());
+        assertEquals(ServiceState.STATE_POWER_OFF, sst.getServiceState().getDataRegState());
+        assertEquals(1, sst.mPollingContext[0]);
+    }
+
+    @Test
     public void testCSEmergencyRegistrationState() throws Exception {
         CellIdentityGsm cellIdentity =
                 new CellIdentityGsm(0, 1, 900, 5, "001", "01", "test", "tst",
@@ -3233,7 +3387,8 @@
         CellIdentityGsm cellIdentity =
                 new CellIdentityGsm(0, 1, 900, 5, "101", "23", "test", "tst",
                         Collections.emptyList());
-        doReturn(Arrays.asList("10123")).when(mSatelliteController).getSatellitePlmnList();
+        doReturn(Arrays.asList("10123")).when(mSatelliteController).getSatellitePlmnsForCarrier(
+                anyInt());
         doReturn(satelliteSupportedServiceList).when(mSatelliteController)
                 .getSupportedSatelliteServices(sst.mSubId, "10123");
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
index 01f06ea..7e4cb08 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.ServiceState.STATE_IN_SERVICE;
+import static android.telephony.ServiceState.STATE_OUT_OF_SERVICE;
+import static android.telephony.ServiceState.STATE_POWER_OFF;
 import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP;
 import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI;
 import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP;
@@ -27,14 +30,19 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -51,10 +59,11 @@
 import android.telephony.SignalStrengthUpdateRequest;
 import android.telephony.SignalThresholdInfo;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.MediumTest;
+
 import com.android.internal.util.ArrayUtils;
 
 import org.junit.After;
@@ -959,6 +968,548 @@
         assertThat(msgCaptor.getValue().what).isEqualTo(ssChangedEvent);
     }
 
+    @Test
+    public void testSignalStrengthLevelUpdatedDueToCarrierConfigChanged() {
+        Handler mockRegistrant = Mockito.mock(Handler.class);
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+        int ssChangedEvent = 0;
+        mSsc.registerForSignalStrengthChanged(mockRegistrant, ssChangedEvent, null);
+
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(
+                        -110, /* rssi */
+                        -114, /* rsrp */
+                        -5, /* rsrq */
+                        0, /* rssnr */
+                        SignalStrength.INVALID, /* cqi */
+                        SignalStrength.INVALID /* ta */),
+                new CellSignalStrengthNr());
+
+        mBundle.putBoolean(CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, true);
+
+        sendCarrierConfigUpdate();
+        verify(mockRegistrant).sendMessageDelayed(msgCaptor.capture(), Mockito.anyLong());
+        assertThat(msgCaptor.getValue().what).isEqualTo(ssChangedEvent);
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                mSsc.getSignalStrength().getLevel());
+
+        Mockito.clearInvocations(mockRegistrant);
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        // Default thresholds are POOR=-115 MODERATE=-105 GOOD=-95 GREAT=-85
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, mSsc.getSignalStrength().getLevel());
+        verify(mockRegistrant).sendMessageDelayed(msgCaptor.capture(), Mockito.anyLong());
+        assertThat(msgCaptor.getValue().what).isEqualTo(ssChangedEvent);
+
+        Mockito.clearInvocations(mockRegistrant);
+        int[] lteThresholds = {
+                -130, // SIGNAL_STRENGTH_POOR
+                -120, // SIGNAL_STRENGTH_MODERATE
+                -110, // SIGNAL_STRENGTH_GOOD
+                -100,  // SIGNAL_STRENGTH_GREAT
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY, lteThresholds);
+        sendCarrierConfigUpdate();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_MODERATE,
+                mSsc.getSignalStrength().getLevel());
+        verify(mockRegistrant).sendMessageDelayed(msgCaptor.capture(), Mockito.anyLong());
+        assertThat(msgCaptor.getValue().what).isEqualTo(ssChangedEvent);
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_GERAN_RSSI_arrayIsTooLong() {
+        mBundle.putIntArray(CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -109, /* SIGNAL_STRENGTH_POOR */
+                        -103, /* SIGNAL_STRENGTH_MODERATE */
+                        -97, /* SIGNAL_STRENGTH_GOOD */
+                        -89,  /* SIGNAL_STRENGTH_GREAT */
+                        -80, /* and extra value */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_GERAN_RSSI_arrayIsTooShort() {
+        mBundle.putIntArray(CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY,
+                new int[]{});
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_GERAN_RSSI_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-113, -51]
+        mBundle.putIntArray(CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -114, /* SIGNAL_STRENGTH_POOR */
+                        -103, /* SIGNAL_STRENGTH_MODERATE */
+                        -97, /* SIGNAL_STRENGTH_GOOD */
+                        -89,  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_GERAN_RSSI_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-113, -51]
+        mBundle.putIntArray(CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -109, /* SIGNAL_STRENGTH_POOR */
+                        -103, /* SIGNAL_STRENGTH_MODERATE */
+                        -97, /* SIGNAL_STRENGTH_GOOD */
+                        -89,  /* SIGNAL_STRENGTH_GREAT */
+                        -50, /* and extra value */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_UTRAN_RSCP_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-120, -24]
+        mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -121, /* SIGNAL_STRENGTH_POOR */
+                        -104, /* SIGNAL_STRENGTH_MODERATE */
+                        -94,  /* SIGNAL_STRENGTH_GOOD */
+                        -84   /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_UTRAN_RSCP_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-120, -24]
+        mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -114, /* SIGNAL_STRENGTH_POOR */
+                        -104, /* SIGNAL_STRENGTH_MODERATE */
+                        -94,  /* SIGNAL_STRENGTH_GOOD */
+                        -23   /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_EUTRAN_RSRP_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-140, -44]
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -141, /* SIGNAL_STRENGTH_POOR */
+                        -118, /* SIGNAL_STRENGTH_MODERATE */
+                        -108, /* SIGNAL_STRENGTH_GOOD */
+                        -98,  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_EUTRAN_RSRP_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-140, -44]
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -128, /* SIGNAL_STRENGTH_POOR */
+                        -118, /* SIGNAL_STRENGTH_MODERATE */
+                        -108, /* SIGNAL_STRENGTH_GOOD */
+                        -43,  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_EUTRAN_RSRQ_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-34, 3]
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -35,  /* SIGNAL_STRENGTH_POOR */
+                        -17,  /* SIGNAL_STRENGTH_MODERATE */
+                        -14,  /* SIGNAL_STRENGTH_GOOD */
+                        -11   /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_EUTRAN_RSRQ_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-34, 3]
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -20,  /* SIGNAL_STRENGTH_POOR */
+                        -17,  /* SIGNAL_STRENGTH_MODERATE */
+                        -14,  /* SIGNAL_STRENGTH_GOOD */
+                        4   /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_EUTRAN_RSSNR_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-20, 30]
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -21,  /* SIGNAL_STRENGTH_POOR */
+                        1,   /* SIGNAL_STRENGTH_MODERATE */
+                        5,   /* SIGNAL_STRENGTH_GOOD */
+                        13   /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_EUTRAN_RSSNR_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-20, 30]
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -3,  /* SIGNAL_STRENGTH_POOR */
+                        1,   /* SIGNAL_STRENGTH_MODERATE */
+                        5,   /* SIGNAL_STRENGTH_GOOD */
+                        31   /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NGRAN_SSRSRP_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-140, -44]
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -141, /* SIGNAL_STRENGTH_POOR */
+                        -107, /* SIGNAL_STRENGTH_MODERATE */
+                        -100, /* SIGNAL_STRENGTH_GOOD */
+                        -95,  /* SIGNAL_STRENGTH_GREAT */
+                        -90, /* and extra value */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NGRAN_SSRSRP_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-140, -44]
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -113, /* SIGNAL_STRENGTH_POOR */
+                        -107, /* SIGNAL_STRENGTH_MODERATE */
+                        -100, /* SIGNAL_STRENGTH_GOOD */
+                        -95,  /* SIGNAL_STRENGTH_GREAT */
+                        -45, /* and extra value */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NGRAN_SSRSRQ_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-43, 20]
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -44, /* SIGNAL_STRENGTH_POOR */
+                        -19, /* SIGNAL_STRENGTH_MODERATE */
+                        -7, /* SIGNAL_STRENGTH_GOOD */
+                        6  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NGRAN_SSRSRQ_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-43, 20]
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -31, /* SIGNAL_STRENGTH_POOR */
+                        -19, /* SIGNAL_STRENGTH_MODERATE */
+                        -7, /* SIGNAL_STRENGTH_GOOD */
+                        21  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NGRAN_SSSINR_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-23, 40]
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -24, /* SIGNAL_STRENGTH_POOR */
+                        5, /* SIGNAL_STRENGTH_MODERATE */
+                        15, /* SIGNAL_STRENGTH_GOOD */
+                        30  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NGRAN_SSSINR_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-24, 1]
+        mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_ECNO_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -25, /* SIGNAL_STRENGTH_POOR */
+                        -14, /* SIGNAL_STRENGTH_MODERATE */
+                        -6, /* SIGNAL_STRENGTH_GOOD */
+                        1  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_UTRAN_ECNO_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-24, 1]
+        mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_ECNO_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -24, /* SIGNAL_STRENGTH_POOR */
+                        -14, /* SIGNAL_STRENGTH_MODERATE */
+                        -6, /* SIGNAL_STRENGTH_GOOD */
+                        2  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_UTRAN_ECNO_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-23, 40]
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -5, /* SIGNAL_STRENGTH_POOR */
+                        5, /* SIGNAL_STRENGTH_MODERATE */
+                        15, /* SIGNAL_STRENGTH_GOOD */
+                        41  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NTN_LTE_RSRP_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-140, -44]
+        mBundle.putIntArray(CarrierConfigManager.KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -141, /* SIGNAL_STRENGTH_POOR */
+                        -118, /* SIGNAL_STRENGTH_MODERATE */
+                        -108, /* SIGNAL_STRENGTH_GOOD */
+                        -98  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+
+    @Test
+    public void testInvalidCarrierConfig_NTN_LTE_RSRP_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-140, -44]
+        mBundle.putIntArray(CarrierConfigManager.KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -128, /* SIGNAL_STRENGTH_POOR */
+                        -118, /* SIGNAL_STRENGTH_MODERATE */
+                        -108, /* SIGNAL_STRENGTH_GOOD */
+                        -43,  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NTN_LTE_RSRQ_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-34, 3]
+        mBundle.putIntArray(CarrierConfigManager.KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -35, /* SIGNAL_STRENGTH_POOR */
+                        -17, /* SIGNAL_STRENGTH_MODERATE */
+                        -14, /* SIGNAL_STRENGTH_GOOD */
+                        -11  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+
+    @Test
+    public void testInvalidCarrierConfig_NTN_LTE_RSRQ_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-34, 3]
+        mBundle.putIntArray(CarrierConfigManager.KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -20, /* SIGNAL_STRENGTH_POOR */
+                        -17, /* SIGNAL_STRENGTH_MODERATE */
+                        -14, /* SIGNAL_STRENGTH_GOOD */
+                        4  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NTN_LTE_RSSNR_thresholdIsTooSmall() {
+        // 4 threshold integers must be within the boundaries [-20, 30]
+        mBundle.putIntArray(CarrierConfigManager.KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -21, /* SIGNAL_STRENGTH_POOR */
+                        1,  /* SIGNAL_STRENGTH_MODERATE */
+                        5,  /* SIGNAL_STRENGTH_GOOD */
+                        13  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testInvalidCarrierConfig_NTN_LTE_RSSNR_thresholdIsTooLarge() {
+        // 4 threshold integers must be within the boundaries [-20, 30]
+        mBundle.putIntArray(CarrierConfigManager.KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                new int[]{
+                        -3, /* SIGNAL_STRENGTH_POOR */
+                        1,  /* SIGNAL_STRENGTH_MODERATE */
+                        5,  /* SIGNAL_STRENGTH_GOOD */
+                        31  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sendCarrierConfigUpdate();
+    }
+
+    @Test
+    public void testLteSignalStrengthReportingCriteriaWhenServiceStateChanged() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(
+                        -110, /* rssi */
+                        -114, /* rsrp */
+                        -5, /* rsrq */
+                        0, /* rssnr */
+                        SignalStrength.INVALID, /* cqi */
+                        SignalStrength.INVALID /* ta */),
+                new CellSignalStrengthNr());
+
+        // RSRP NTN_LTE threshold set to Good and LTE threshold set to poor.
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT,
+                CellSignalStrengthLte.USE_RSRP);
+        mBundle.putIntArray(CarrierConfigManager.KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                new int[]{-125 /* SIGNAL_STRENGTH_POOR */, -120 /* SIGNAL_STRENGTH_MODERATE */,
+                        -115 /* SIGNAL_STRENGTH_GOOD */, -110/* SIGNAL_STRENGTH_GREAT */});
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                new int[]{-114, /* SIGNAL_STRENGTH_POOR */ -110, /* SIGNAL_STRENGTH_MODERATE */
+                        -105, /* SIGNAL_STRENGTH_GOOD */ -100, /* SIGNAL_STRENGTH_GREAT */});
+        CarrierConfigManager mockConfigManager = Mockito.mock(CarrierConfigManager.class);
+        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+                .thenReturn(mockConfigManager);
+        when(mockConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle);
+
+        // When NTN is connected, check the signal strength is GOOD
+        AsyncResult asyncResult = mock(AsyncResult.class);
+        asyncResult.result = mServiceState;
+        doReturn(true).when(mServiceState).isUsingNonTerrestrialNetwork();
+        mSsc.handleMessage(mSsc.obtainMessage(10/*EVENT_SERVICE_STATE_CHANGED*/, asyncResult));
+        processAllMessages();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GOOD, mSsc.getSignalStrength().getLevel());
+
+        // When TN connected, check the signal strength is POOR
+        doReturn(false).when(mServiceState).isUsingNonTerrestrialNetwork();
+        mSsc.handleMessage(mSsc.obtainMessage(10/*EVENT_SERVICE_STATE_CHANGED*/, asyncResult));
+        processAllMessages();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, mSsc.getSignalStrength().getLevel());
+
+        // RSRP NTN_LTE threshold set to Moderate and LTE threshold set to poor.
+        // When TN connected, check the signal strength is POOR.
+        mBundle.putIntArray(CarrierConfigManager.KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                new int[]{-130 /* SIGNAL_STRENGTH_POOR */, -120 /* SIGNAL_STRENGTH_MODERATE */,
+                        -110 /* SIGNAL_STRENGTH_GOOD */, -100/* SIGNAL_STRENGTH_GREAT */});
+        mSsc.handleMessage(mSsc.obtainMessage(10/*EVENT_SERVICE_STATE_CHANGED*/, asyncResult));
+        processAllMessages();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, mSsc.getSignalStrength().getLevel());
+
+        // Service State Changed with OUT_OF_SERVICE, then no update
+        // SignalStrengthReportingCriteria.
+        reset(mSimulatedCommandsVerifier);
+        doReturn(STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+        mSsc.handleMessage(mSsc.obtainMessage(10/*EVENT_SERVICE_STATE_CHANGED*/, asyncResult));
+        processAllMessages();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, mSsc.getSignalStrength().getLevel());
+        verify(mSimulatedCommandsVerifier, never()).setSignalStrengthReportingCriteria(anyList(),
+                isNull());
+
+        // Service State Changed with POWER_OFF, then no update SignalStrengthReportingCriteria.
+        reset(mSimulatedCommandsVerifier);
+        doReturn(STATE_POWER_OFF).when(mServiceState).getState();
+        mSsc.handleMessage(mSsc.obtainMessage(10/*EVENT_SERVICE_STATE_CHANGED*/, asyncResult));
+        processAllMessages();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, mSsc.getSignalStrength().getLevel());
+        verify(mSimulatedCommandsVerifier, never()).setSignalStrengthReportingCriteria(anyList(),
+                isNull());
+
+        // Service State Changed with IN_SERVICE, then update SignalStrengthReportingCriteria.
+        // When NTN is connected, check the signal strength is MODERATE
+        reset(mSimulatedCommandsVerifier);
+        doReturn(true).when(mServiceState).isUsingNonTerrestrialNetwork();
+        doReturn(STATE_IN_SERVICE).when(mServiceState).getState();
+        mSsc.handleMessage(mSsc.obtainMessage(10/*EVENT_SERVICE_STATE_CHANGED*/, asyncResult));
+        processAllMessages();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_MODERATE,
+                mSsc.getSignalStrength().getLevel());
+        verify(mSimulatedCommandsVerifier).setSignalStrengthReportingCriteria(anyList(), isNull());
+
+        // Service State Changed with IN_SERVICE and still NTN is connected,
+        // verify not update SignalStrengthReportingCriteria and the signal strength is MODERATE.
+        reset(mSimulatedCommandsVerifier);
+        mSsc.handleMessage(mSsc.obtainMessage(10/*EVENT_SERVICE_STATE_CHANGED*/, asyncResult));
+        processAllMessages();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_MODERATE,
+                mSsc.getSignalStrength().getLevel());
+        verify(mSimulatedCommandsVerifier, never()).setSignalStrengthReportingCriteria(anyList(),
+                isNull());
+
+        // Service State Changed with IN_SERVICE, then update SignalStrengthReportingCriteria.
+        // When TN is connected, check the signal strength is POOR.
+        reset(mSimulatedCommandsVerifier);
+        doReturn(false).when(mServiceState).isUsingNonTerrestrialNetwork();
+        mSsc.handleMessage(mSsc.obtainMessage(10/*EVENT_SERVICE_STATE_CHANGED*/, asyncResult));
+        processAllMessages();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR,
+                mSsc.getSignalStrength().getLevel());
+        verify(mSimulatedCommandsVerifier).setSignalStrengthReportingCriteria(anyList(), isNull());
+
+        // Service State Changed with IN_SERVICE and still TN is connected,
+        // verify not update SignalStrengthReportingCriteria and the signal strength is POOR.
+        reset(mSimulatedCommandsVerifier);
+        mSsc.handleMessage(mSsc.obtainMessage(10/*EVENT_SERVICE_STATE_CHANGED*/, asyncResult));
+        processAllMessages();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR,
+                mSsc.getSignalStrength().getLevel());
+        verify(mSimulatedCommandsVerifier, never()).setSignalStrengthReportingCriteria(anyList(),
+                isNull());
+
+        reset(mSimulatedCommandsVerifier);
+    }
+
     private void verifyAllEmptyThresholdAreDisabledWhenSetSignalStrengthReportingCriteria(
             int expectedNonEmptyThreshold) {
         ArgumentCaptor<List<SignalThresholdInfo>> signalThresholdInfoCaptor =
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
index 96184c5..8df4052 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
@@ -21,6 +21,7 @@
 
 import android.os.Parcel;
 import android.os.PersistableBundle;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellInfo;
 import android.telephony.CellSignalStrength;
@@ -30,10 +31,16 @@
 import android.telephony.CellSignalStrengthNr;
 import android.telephony.CellSignalStrengthTdscdma;
 import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.telephony.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -46,6 +53,7 @@
 @SmallTest
 @RunWith(JUnit4.class)
 public class SignalStrengthTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final int[] DEFAULT_LTE_RSRP_THRESHOLDS = {
             -128,  // SIGNAL_STRENGTH_POOR
             -118,  // SIGNAL_STRENGTH_MODERATE
@@ -70,6 +78,42 @@
             -105,  // SIGNAL_STRENGTH_GOOD
             -95 }; // SIGNAL_STRENGTH_GREAT
 
+    private static final int[] DEFAULT_NTN_LTE_RSRP_THRESHOLDS = {
+            -118,  // SIGNAL_STRENGTH_POOR
+            -108,  // SIGNAL_STRENGTH_MODERATE
+            -98,  // SIGNAL_STRENGTH_GOOD
+            -88 }; // SIGNAL_STRENGTH_GREAT
+
+    private static final int[] DEFAULT_NTN_LTE_RSRQ_THRESHOLDS = {
+            -17,   // SIGNAL_STRENGTH_POOR
+            -14,   // SIGNAL_STRENGTH_MODERATE
+            -12,   // SIGNAL_STRENGTH_GOOD
+            -10 }; // SIGNAL_STRENGTH_GREAT
+
+    private static final int[] DEFAULT_NTN_LTE_RSSNR_THRESHOLDS = {
+            1,   // SIGNAL_STRENGTH_POOR
+            5,    // SIGNAL_STRENGTH_MODERATE
+            13,    // SIGNAL_STRENGTH_GOOD
+            17 }; // SIGNAL_STRENGTH_GREAT
+
+    // RSRP, RSSNR thresholds boundaries
+    private static final int MIN_RSRP = -140;
+    private static final int MIN_RSRQ = -34;
+    private static final int MAX_RSRQ = 3;
+    private static final int MIN_RSSNR = -20;
+    private static final int MAX_RSSNR = 30;
+
+    // Default NTN & TN LTE thresholds's index
+    private static final int INDEX_SIGNAL_STRENGTH_POOR = 0;
+    private static final int INDEX_SIGNAL_STRENGTH_MODERATE = 1;
+    private static final int INDEX_SIGNAL_STRENGTH_GOOD = 2;
+    private static final int INDEX_SIGNAL_STRENGTH_GREAT = 3;
+
+    @Before
+    public void setUp() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+    }
+
     @Test
     public void testDefaults() throws Exception {
         SignalStrength s = new SignalStrength();
@@ -229,6 +273,58 @@
         return signalStrength;
     }
 
+    private static SignalStrength createSignalStrengthLteReport(int lteRsrp, int lteRsrq,
+            int lteRssnr, boolean isNTN) {
+        CellSignalStrengthLte lte = new CellSignalStrengthLte(
+                -89,               // rssi
+                lteRsrp,               // rsrp
+                lteRsrq,               // rsrq
+                lteRssnr,              // rssnr
+                CellInfo.UNAVAILABLE,  // cqiTableIndex
+                CellInfo.UNAVAILABLE,  // cqi
+                CellInfo.UNAVAILABLE); // timingAdvance
+
+        SignalStrength signalStrength = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                lte,
+                new CellSignalStrengthNr());
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(
+                CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
+                CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSRQ
+                        | CellSignalStrengthLte.USE_RSSNR);
+        bundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSRP_THRESHOLDS);
+        bundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSRQ_THRESHOLDS);
+        bundle.putIntArray(CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSSNR_THRESHOLDS);
+        bundle.putInt(
+                CarrierConfigManager.KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT,
+                CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSRQ
+                        | CellSignalStrengthLte.USE_RSSNR);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                DEFAULT_NTN_LTE_RSRP_THRESHOLDS);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                DEFAULT_NTN_LTE_RSRQ_THRESHOLDS);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                DEFAULT_NTN_LTE_RSSNR_THRESHOLDS);
+        ServiceState serviceState = new ServiceState();
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setIsNonTerrestrialNetwork(isNTN)
+                .build();
+        serviceState.addNetworkRegistrationInfo(nri);
+        signalStrength.updateLevel(bundle, serviceState);
+        return signalStrength;
+    }
+
     @Test
     public void testValidateInput() throws Exception {
 
@@ -265,6 +361,44 @@
         // Input value of RSSNR: -21[dB]
         ss = createSignalStrengthLteReportRssnr(60, -21);
         assertEquals(SignalStrength.INVALID, ss.getLteRssnr());
+
+        // Test for NTN LTE RSRQ Thresholds based on Boundaries [-34 dB, 3 dB]
+        boolean isNTN = true;
+        int rsrp = DEFAULT_NTN_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rsrq = MAX_RSRQ + 1;
+        ss = createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN);
+        assertEquals(SignalStrength.INVALID, ss.getLteRsrq());
+
+        rsrq = MAX_RSRQ;
+        ss = createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN);
+        assertEquals(3, ss.getLteRsrq());
+
+        rsrq = MIN_RSRQ - 1;
+        ss = createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN);
+        assertEquals(SignalStrength.INVALID, ss.getLteRsrq());
+
+        rsrq = MIN_RSRQ;
+        ss = createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN);
+        assertEquals(-34, ss.getLteRsrq());
+
+        // Test for NTN LTE RSSNR Thresholds based on Boundaries [-20 dBm, 30 dBm]
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT]; // or 3 ?
+        rssnr = MAX_RSSNR + 1;
+        ss = createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN);
+        assertEquals(SignalStrength.INVALID, ss.getLteRssnr());
+
+        rssnr = MAX_RSSNR;
+        ss = createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN);
+        assertEquals(30, ss.getLteRssnr());
+
+        rssnr = MIN_RSSNR - 1;
+        ss = createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN);
+        assertEquals(SignalStrength.INVALID, ss.getLteRssnr());
+
+        rssnr = MIN_RSSNR;
+        ss = createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN);
+        assertEquals(-20, ss.getLteRssnr());
     }
 
     @Test
@@ -279,6 +413,46 @@
                 createSignalStrengthLteReportRsrq(-98, -14).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_GREAT,
                 createSignalStrengthLteReportRsrq(-98, -12).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = DEFAULT_NTN_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GREAT,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rsrp = DEFAULT_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GREAT,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 
     @Test
@@ -293,6 +467,46 @@
                 createSignalStrengthLteReportRsrq(-108, -14).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
                 createSignalStrengthLteReportRsrq(-108, -12).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = DEFAULT_NTN_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        int rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rsrp = DEFAULT_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 
     @Test
@@ -307,6 +521,46 @@
                 createSignalStrengthLteReportRsrq(-118, -14).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
                 createSignalStrengthLteReportRsrq(-118, -12).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = DEFAULT_NTN_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        int rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rsrp = DEFAULT_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 
     @Test
@@ -321,6 +575,46 @@
                 createSignalStrengthLteReportRsrq(-128, -14).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
                 createSignalStrengthLteReportRsrq(-128, -12).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = DEFAULT_NTN_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        int rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rsrp = DEFAULT_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 
     @Test
@@ -335,6 +629,45 @@
                 createSignalStrengthLteReportRsrq(-138, -14).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
                 createSignalStrengthLteReportRsrq(-138, -12).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = MIN_RSRP;
+        int rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rsrq = MIN_RSRQ;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 
     @Test
@@ -349,6 +682,46 @@
                 createSignalStrengthLteReportRssnr(-98, 5).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_GREAT,
                 createSignalStrengthLteReportRssnr(-98, 13).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = DEFAULT_NTN_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GREAT,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rsrp = DEFAULT_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GREAT,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 
     @Test
@@ -363,6 +736,46 @@
                 createSignalStrengthLteReportRssnr(-108, 5).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
                 createSignalStrengthLteReportRssnr(-108, 13).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = DEFAULT_NTN_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        int rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rsrp = DEFAULT_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 
     @Test
@@ -377,6 +790,46 @@
                 createSignalStrengthLteReportRssnr(-118, 5).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
                 createSignalStrengthLteReportRssnr(-118, 13).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = DEFAULT_NTN_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        int rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rsrp = DEFAULT_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 
     @Test
@@ -391,6 +844,46 @@
                 createSignalStrengthLteReportRssnr(-128, 5).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
                 createSignalStrengthLteReportRssnr(-128, 13).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = DEFAULT_NTN_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        int rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rsrp = DEFAULT_LTE_RSRP_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 
     @Test
@@ -405,6 +898,45 @@
                 createSignalStrengthLteReportRssnr(-138, 5).getLteLevel());
         assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
                 createSignalStrengthLteReportRssnr(-138, 13).getLteLevel());
+
+        // When NTN is connected, check the signal strength
+        boolean isNTN = true;
+        int rsrp = MIN_RSRP;
+        int rsrq = DEFAULT_NTN_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        int rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_NTN_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+
+        // When NTN is disconnected, check the signal strength
+        isNTN = false;
+        rsrq = DEFAULT_LTE_RSRQ_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        rssnr = MIN_RSSNR;
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_POOR];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_MODERATE];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GOOD];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
+        rssnr = DEFAULT_LTE_RSSNR_THRESHOLDS[INDEX_SIGNAL_STRENGTH_GREAT];
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReport(rsrp, rsrq, rssnr, isNTN).getLteLevel());
     }
 }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalToneUtilTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalToneUtilTest.java
index d7eef36..599b549 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalToneUtilTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalToneUtilTest.java
@@ -15,12 +15,16 @@
  */
 package com.android.internal.telephony;
 
-import android.media.ToneGenerator;
-import android.test.suitebuilder.annotation.SmallTest;
-import com.android.internal.telephony.cdma.SignalToneUtil;
-import org.junit.Test;
 import static org.junit.Assert.assertEquals;
 
+import android.media.ToneGenerator;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.telephony.cdma.SignalToneUtil;
+
+import org.junit.Test;
+
 public class SignalToneUtilTest {
     /* no need to initialization, everything is static */
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
index ea6f19d..7c8c7ec 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
@@ -19,9 +19,9 @@
 import android.content.ContentValues;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
-import android.test.suitebuilder.annotation.Suppress;
 
-import com.android.internal.telephony.IccProvider;
+import androidx.test.filters.Suppress;
+
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.internal.telephony.uicc.IccConstants;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java
index 48406de..35a61fc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java
@@ -18,8 +18,9 @@
 
 import android.app.ActivityThread;
 import android.telephony.TelephonyFrameworkInitializer;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.Suppress;
 
 import junit.framework.TestCase;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
index c79f17f..77612d2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimUtilsTest.java
@@ -16,12 +16,11 @@
 
 package com.android.internal.telephony;
 
-import com.android.internal.telephony.gsm.SimTlv;
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.uicc.IccUtils;
 
 import junit.framework.TestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
 
 public class SimUtilsTest extends TestCase {
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
index 1e4c939..39c0cac 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
@@ -18,9 +18,10 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.radio.RadioError;
-import android.hardware.radio.V1_0.DataRegStateResult;
-import android.hardware.radio.V1_0.SetupDataCallResult;
-import android.hardware.radio.V1_0.VoiceRegStateResult;
+import android.hardware.radio.V1_2.VoiceRegStateResult;
+import android.hardware.radio.V1_4.DataRegStateResult;
+import android.hardware.radio.V1_4.PdpProtocolType;
+import android.hardware.radio.V1_4.SetupDataCallResult;
 import android.hardware.radio.modem.ImeiInfo;
 import android.net.KeepalivePacketData;
 import android.net.LinkProperties;
@@ -76,7 +77,6 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccIoResult;
-import com.android.internal.telephony.uicc.IccSlotStatus;
 import com.android.internal.telephony.uicc.ReceivedPhonebookRecords;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
 import com.android.telephony.Rlog;
@@ -89,7 +89,6 @@
 public class SimulatedCommands extends BaseCommands
         implements CommandsInterface, SimulatedRadioControl {
     private static final String LOG_TAG = "SimulatedCommands";
-    private boolean mSupportsEid = true;
 
     private enum SimLockState {
         NONE,
@@ -127,9 +126,6 @@
     // arrive and returning null to the callers.
     public static final  long ICC_SIM_CHALLENGE_TIMEOUT_MILLIS = 2500;
 
-    private String mImei;
-    private String mImeiSv;
-
     //***** Instance Variables
 
     @UnsupportedAppUsage
@@ -167,7 +163,6 @@
     private boolean mShouldReturnCellInfo = true;
     private int[] mImsRegState;
     private IccCardStatus mIccCardStatus;
-    private IccSlotStatus mIccSlotStatus;
     private IccIoResult mIccIoResultForApduLogicalChannel;
     private int mChannelId = IccOpenLogicalChannelResponse.INVALID_CHANNEL;
 
@@ -175,7 +170,7 @@
     private Object mVoiceRegStateResult;
 
     int mPausedResponseCount;
-    ArrayList<Message> mPausedResponses = new ArrayList<Message>();
+    ArrayList<Message> mPausedResponses = new ArrayList<>();
 
     int mNextCallFailCause = CallFailCause.NORMAL_CLEARING;
 
@@ -242,26 +237,6 @@
         }
     }
 
-    public void setIccSlotStatus(IccSlotStatus iccSlotStatus) {
-        mIccSlotStatus = iccSlotStatus;
-    }
-
-    @Override
-    public void getIccSlotsStatus(Message result) {
-        SimulatedCommandsVerifier.getInstance().getIccSlotsStatus(result);
-        if (mIccSlotStatus != null) {
-            resultSuccess(result, mIccSlotStatus);
-        } else {
-            resultFail(result, null,
-                    new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED));
-        }
-    }
-
-    @Override
-    public void setLogicalToPhysicalSlotMapping(int[] physicalSlots, Message result) {
-        unimplemented(result);
-    }
-
     @Override
     public void supplyIccPin(String pin, Message result)  {
         if (mSimLockedState != SimLockState.REQUIRE_PIN) {
@@ -575,15 +550,6 @@
     }
 
     /**
-     *  @deprecated
-     */
-    @Deprecated
-    @Override
-    public void getPDPContextList(Message result) {
-        getDataCallList(result);
-    }
-
-    /**
      *  returned message
      *  retMsg.obj = AsyncResult ar
      *  ar.exception carries exception on failure
@@ -592,7 +558,7 @@
      */
     @Override
     public void getDataCallList(Message result) {
-        ArrayList<SetupDataCallResult> dcCallList = new ArrayList<SetupDataCallResult>(0);
+        ArrayList<SetupDataCallResult> dcCallList = new ArrayList<>(0);
         SimulatedCommandsVerifier.getInstance().getDataCallList(result);
         if (mSetupDataCallResult != null) {
             dcCallList.add(mSetupDataCallResult);
@@ -659,40 +625,6 @@
         resultSuccess(result, "012345678901234");
     }
 
-    public void setIMEI(String imei) {
-        mImei = imei;
-    }
-
-    /**
-     *  returned message
-     *  retMsg.obj = AsyncResult ar
-     *  ar.exception carries exception on failure
-     *  ar.userObject contains the original value of result.obj
-     *  ar.result is String containing IMEI on success
-     */
-    @Override
-    public void getIMEI(Message result) {
-        SimulatedCommandsVerifier.getInstance().getIMEI(result);
-        resultSuccess(result, mImei != null ? mImei : FAKE_IMEI);
-    }
-
-    public void setIMEISV(String imeisv) {
-        mImeiSv = imeisv;
-    }
-
-    /**
-     *  returned message
-     *  retMsg.obj = AsyncResult ar
-     *  ar.exception carries exception on failure
-     *  ar.userObject contains the original value of result.obj
-     *  ar.result is String containing IMEISV on success
-     */
-    @Override
-    public void getIMEISV(Message result) {
-        SimulatedCommandsVerifier.getInstance().getIMEISV(result);
-        resultSuccess(result, mImeiSv != null ? mImeiSv : FAKE_IMEISV);
-    }
-
     /**
      * Hang up one individual connection.
      *  returned message
@@ -900,21 +832,6 @@
         resultSuccess(result, mFailCause);
     }
 
-    /**
-     * @deprecated
-     */
-    @Deprecated
-    @Override
-    public void getLastPdpFailCause (Message result) {
-        unimplemented(result);
-    }
-
-    @Override
-    public void getLastDataCallFailCause(Message result) {
-        //
-        unimplemented(result);
-    }
-
     @Override
     public void setMute (boolean enableMute, Message result) {unimplemented(result);}
 
@@ -1059,10 +976,10 @@
         Object ret = mDataRegStateResult;
         if (ret == null) {
             ret = new DataRegStateResult();
-            ((DataRegStateResult) ret).regState = mDataRegState;
-            ((DataRegStateResult) ret).rat = mDataRadioTech;
-            ((DataRegStateResult) ret).maxDataCalls = mMaxDataCalls;
-            ((DataRegStateResult) ret).reasonDataDenied = mReasonForDenial;
+            ((DataRegStateResult) ret).base.regState = mDataRegState;
+            ((DataRegStateResult) ret).base.rat = mDataRadioTech;
+            ((DataRegStateResult) ret).base.maxDataCalls = mMaxDataCalls;
+            ((DataRegStateResult) ret).base.reasonDataDenied = mReasonForDenial;
         }
 
         resultSuccess(result, ret);
@@ -1097,7 +1014,6 @@
 
     @VisibleForTesting
     public int getGetOperatorCallCount() {
-        final int count = mGetOperatorCallCount.get();
         return mGetOperatorCallCount.get();
     }
 
@@ -1212,32 +1128,31 @@
     }
 
     @Override
-    public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
-            boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
-            NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
-            boolean matchAllRuleAllowed, Message result) {
+    public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean allowRoaming,
+            int reason, LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
+            TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed, Message result) {
 
         SimulatedCommandsVerifier.getInstance().setupDataCall(accessNetworkType, dataProfile,
-                isRoaming, allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
-                trafficDescriptor, matchAllRuleAllowed, result);
+                allowRoaming, reason, linkProperties, pduSessionId, sliceInfo, trafficDescriptor,
+                matchAllRuleAllowed, result);
 
         if (mSetupDataCallResult == null) {
             try {
                 mSetupDataCallResult = new SetupDataCallResult();
-                mSetupDataCallResult.status = 0;
+                mSetupDataCallResult.cause = 0;
                 mSetupDataCallResult.suggestedRetryTime = -1;
                 mSetupDataCallResult.cid = 1;
                 mSetupDataCallResult.active = 2;
-                mSetupDataCallResult.type = "IP";
+                mSetupDataCallResult.type = PdpProtocolType.IP;
                 mSetupDataCallResult.ifname = "rmnet_data7";
-                mSetupDataCallResult.addresses = "12.34.56.78";
-                mSetupDataCallResult.dnses = "98.76.54.32";
-                mSetupDataCallResult.gateways = "11.22.33.44";
-                mSetupDataCallResult.pcscf =
-                        "fd00:976a:c305:1d::8 fd00:976a:c202:1d::7 fd00:976a:c305:1d::5";
+                mSetupDataCallResult.addresses = new ArrayList<>(List.of("12.34.56.78"));
+                mSetupDataCallResult.dnses = new ArrayList<>(List.of("98.76.54.32"));
+                mSetupDataCallResult.gateways = new ArrayList<>(List.of("11.22.33.44"));
+                mSetupDataCallResult.pcscf = new ArrayList<>(List.of(
+                        "fd00:976a:c305:1d::8 fd00:976a:c202:1d::7 fd00:976a:c305:1d::5"));
                 mSetupDataCallResult.mtu = 1440;
             } catch (Exception e) {
-
+                Rlog.e(LOG_TAG, "setupDataCall: e=" + e);
             }
         }
 
@@ -1624,21 +1539,6 @@
         resultSuccess(response, null);
     }
 
-
-    @Override
-    public void resetRadio(Message result) {
-        unimplemented(result);
-    }
-
-    @Override
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-        // Just echo back data
-        if (response != null) {
-            AsyncResult.forMessage(response).result = data;
-            response.sendToTarget();
-        }
-    }
-
     @Override
     public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
                                                 Message response) {
@@ -1649,15 +1549,6 @@
         }
     }
 
-    @Override
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-        // Just echo back data
-        if (response != null) {
-            AsyncResult.forMessage(response).result = strings;
-            response.sendToTarget();
-        }
-    }
-
     //***** SimulatedRadioControl
 
 
@@ -2101,7 +1992,7 @@
         if (!mShouldReturnCellInfo) return;
 
         if (mCellInfoList == null) {
-            ArrayList<CellInfo> mCellInfoList = new ArrayList();
+            mCellInfoList = new ArrayList();
             mCellInfoList.add(getCellInfoGsm());
         }
 
@@ -2119,14 +2010,14 @@
     }
 
     @Override
-    public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, Message result) {
-        SimulatedCommandsVerifier.getInstance().setInitialAttachApn(dataProfile, isRoaming, result);
+    public void setInitialAttachApn(DataProfile dataProfile, Message result) {
+        SimulatedCommandsVerifier.getInstance().setInitialAttachApn(dataProfile, result);
         resultSuccess(result, null);
     }
 
     @Override
-    public void setDataProfile(DataProfile[] dps, boolean isRoaming, Message result) {
-        SimulatedCommandsVerifier.getInstance().setDataProfile(dps, isRoaming, result);
+    public void setDataProfile(DataProfile[] dps, Message result) {
+        SimulatedCommandsVerifier.getInstance().setDataProfile(dps, result);
         resultSuccess(result, null);
     }
 
@@ -2231,22 +2122,6 @@
     }
 
     @Override
-    public void startLceService(int report_interval_ms, boolean pullMode, Message result) {
-        SimulatedCommandsVerifier.getInstance().startLceService(report_interval_ms, pullMode,
-                result);
-    }
-
-    @Override
-    public void stopLceService(Message result) {
-        unimplemented(result);
-    }
-
-    @Override
-    public void pullLceData(Message result) {
-        unimplemented(result);
-    }
-
-    @Override
     public void registerForLceInfo(Handler h, int what, Object obj) {
         SimulatedCommandsVerifier.getInstance().registerForLceInfo(h, what, obj);
     }
@@ -2564,15 +2439,6 @@
                 new ReceivedPhonebookRecords(4, phonebookRecordInfoGroup), null));
     }
 
-    public void setSupportsEid(boolean supportsEid) {
-        mSupportsEid = supportsEid;
-    }
-
-    @Override
-    public boolean supportsEid() {
-        return mSupportsEid;
-    }
-
     @Override
     public void getSimPhonebookCapacity(Message result) {
         resultSuccess(result, new AdnCapacity(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
index 4d1c104..6fc5616 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
@@ -688,11 +688,6 @@
     }
 
     @Override
-    public void getPDPContextList(Message result) {
-
-    }
-
-    @Override
     public void getDataCallList(Message result) {
 
     }
@@ -719,16 +714,6 @@
     }
 
     @Override
-    public void getIMEI(Message result) {
-
-    }
-
-    @Override
-    public void getIMEISV(Message result) {
-
-    }
-
-    @Override
     public void hangupConnection(int gsmIndex, Message result) {
 
     }
@@ -789,16 +774,6 @@
     }
 
     @Override
-    public void getLastPdpFailCause(Message result) {
-
-    }
-
-    @Override
-    public void getLastDataCallFailCause(Message result) {
-
-    }
-
-    @Override
     public void setMute(boolean enableMute, Message response) {
 
     }
@@ -1039,11 +1014,6 @@
     }
 
     @Override
-    public void resetRadio(Message result) {
-
-    }
-
-    @Override
     public void setBandMode(int bandMode, Message response) {
 
     }
@@ -1100,26 +1070,6 @@
     }
 
     @Override
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-
-    }
-
-    @Override
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-
-    }
-
-    @Override
-    public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
-
-    }
-
-    @Override
-    public void unSetOnUnsolOemHookRaw(Handler h) {
-
-    }
-
-    @Override
     public void sendTerminalResponse(String contents, Message response) {
 
     }
@@ -1210,10 +1160,9 @@
     }
 
     @Override
-    public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
-            boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
-            NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
-            boolean matchAllRuleAllowed, Message result) {
+    public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean allowRoaming,
+            int reason, LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
+            TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed, Message result) {
     }
 
     @Override
@@ -1247,14 +1196,6 @@
     }
 
     @Override
-    public void getIccSlotsStatus(Message result) {
-    }
-
-    @Override
-    public void setLogicalToPhysicalSlotMapping(int[] physicalSlots, Message result) {
-    }
-
-    @Override
     public void requestIccSimAuthentication(int authContext, String data, String aid,
                                             Message response) {
 
@@ -1284,12 +1225,12 @@
     }
 
     @Override
-    public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, Message result) {
+    public void setInitialAttachApn(DataProfile dataProfile, Message result) {
 
     }
 
     @Override
-    public void setDataProfile(DataProfile[] dps, boolean isRoaming, Message result) {
+    public void setDataProfile(DataProfile[] dps, Message result) {
 
     }
 
@@ -1378,21 +1319,6 @@
     }
 
     @Override
-    public void startLceService(int reportIntervalMs, boolean pullMode, Message result) {
-
-    }
-
-    @Override
-    public void stopLceService(Message result) {
-
-    }
-
-    @Override
-    public void pullLceData(Message result) {
-
-    }
-
-    @Override
     public void registerForLceInfo(Handler h, int what, Object obj) {
 
     }
@@ -1549,4 +1475,12 @@
     @Override
     public void cancelHandover(Message result, int callId) {
     }
+
+    /**
+     * Register to listen for the changes in the primary IMEI with respect to the sim slot.
+     */
+    @Override
+    public void registerForImeiMappingChanged(Handler h, int what, Object obj) {
+
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/Sms7BitEncodingTranslatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/Sms7BitEncodingTranslatorTest.java
index cc0eb94..3295640 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/Sms7BitEncodingTranslatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/Sms7BitEncodingTranslatorTest.java
@@ -22,7 +22,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
index 09c4173..f8d1bec 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
@@ -16,7 +16,10 @@
 
 package com.android.internal.telephony;
 
+import static junit.framework.Assert.assertEquals;
+
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.anyInt;
@@ -26,7 +29,9 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.pm.PackageManager;
+import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -34,17 +39,25 @@
 import com.android.internal.telephony.uicc.AdnRecordCache;
 import com.android.internal.telephony.uicc.IccConstants;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.NoSuchElementException;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class SmsControllerTest extends TelephonyTest {
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
     // Mocked classes
     private AdnRecordCache mAdnRecordCache;
@@ -58,13 +71,17 @@
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mAdnRecordCache = Mockito.mock(AdnRecordCache.class);
-        mSmsControllerUT = new SmsController(mContext);
+        mSmsControllerUT = new SmsController(mContext, mFeatureFlags);
         mCallingPackage = mContext.getOpPackageName();
+
+        doReturn(true).when(mPackageManager).hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING);
     }
 
     @After
     public void tearDown() throws Exception {
         mAdnRecordCache = null;
+        WapPushCache.clear();
         super.tearDown();
     }
 
@@ -239,4 +256,65 @@
         verify(mIccSmsInterfaceManager, Mockito.times(0))
                 .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true);
     }
+
+    @Test
+    public void testGetWapMessageSize() {
+        long expectedSize = 100L;
+        String location = "content://mms";
+        byte[] locationBytes = location.getBytes(StandardCharsets.ISO_8859_1);
+        byte[] transactionId = "123".getBytes(StandardCharsets.ISO_8859_1);
+
+        WapPushCache.putWapMessageSize(locationBytes, transactionId, expectedSize);
+        long size = mSmsControllerUT.getWapMessageSize(location);
+
+        assertEquals(expectedSize, size);
+    }
+
+    @Test
+    public void testGetWapMessageSize_withTransactionIdAppended() {
+        long expectedSize = 100L;
+        byte[] location = "content://mms".getBytes(StandardCharsets.ISO_8859_1);
+        byte[] transactionId = "123".getBytes(StandardCharsets.ISO_8859_1);
+        byte[] joinedKey = new byte[location.length + transactionId.length];
+        System.arraycopy(location, 0, joinedKey, 0, location.length);
+        System.arraycopy(transactionId, 0, joinedKey, location.length, transactionId.length);
+        String joinedKeyString = new String(joinedKey, StandardCharsets.ISO_8859_1);
+
+        WapPushCache.putWapMessageSize(location, transactionId, expectedSize);
+        long size = mSmsControllerUT.getWapMessageSize(joinedKeyString);
+
+        assertEquals(expectedSize, size);
+    }
+
+    @Test
+    public void testGetWapMessageSize_nonexistentThrows() {
+        assertThrows(NoSuchElementException.class, () ->
+                mSmsControllerUT.getWapMessageSize("content://mms")
+        );
+    }
+
+    @Test
+    @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
+    public void sendTextForSubscriberTestEnabledTelephonyFeature() {
+        int subId = 1;
+        doReturn(true).when(mSubscriptionManager)
+                .isSubscriptionAssociatedWithUser(eq(subId), any());
+
+        // Feature enabled, device does not have required telephony feature.
+        doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+        doReturn(false).when(mPackageManager).hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING);
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234",
+                        null, "text", null, null, false, 0L, true, true));
+
+        // Device has required telephony feature.
+        doReturn(true).when(mPackageManager).hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING);
+        mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234",
+                null, "text", null, null, false, 0L, true, true);
+        verify(mIccSmsInterfaceManager, Mockito.times(1))
+                .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true);
+    }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
index b073cd4..3fa4fd1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
@@ -23,10 +23,12 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -50,15 +52,18 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SmsManager;
 import android.test.FlakyTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Singleton;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.ims.ImsManager;
 import com.android.internal.telephony.domainselection.DomainSelectionConnection;
 import com.android.internal.telephony.domainselection.EmergencySmsDomainSelectionConnection;
 import com.android.internal.telephony.domainselection.SmsDomainSelectionConnection;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.IccUtils;
 
 import org.junit.After;
@@ -80,8 +85,8 @@
      */
     private static class TestSmsDispatchersController extends SmsDispatchersController {
         TestSmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor,
-                SmsUsageMonitor usageMonitor, Looper looper) {
-            super(phone, storageMonitor, usageMonitor, looper);
+                SmsUsageMonitor usageMonitor, Looper looper, FeatureFlags featureFlags) {
+            super(phone, storageMonitor, usageMonitor, looper, featureFlags);
         }
 
         public DomainSelectionConnectionHolder testGetDomainSelectionConnectionHolder(
@@ -104,6 +109,15 @@
             sendMultipartText(destAddr, scAddr, parts, sentIntents, deliveryIntents, messageUri,
                     callingPkg, persistMessage, priority, expectMore, validityPeriod, messageId);
         }
+
+        public void testNotifySmsSentToEmergencyStateTracker(String destAddr, long messageId) {
+            notifySmsSentToEmergencyStateTracker(destAddr, messageId);
+        }
+
+        public void testNotifySmsSentFailedToEmergencyStateTracker(String destAddr,
+                long messageId) {
+            notifySmsSentFailedToEmergencyStateTracker(destAddr, messageId);
+        }
     }
 
     /**
@@ -158,6 +172,7 @@
     private static final String ACTION_TEST_SMS_SENT = "TEST_SMS_SENT";
 
     // Mocked classes
+    private FeatureFlags mFeatureFlags;
     private SMSDispatcher.SmsTracker mTracker;
     private PendingIntent mSentIntent;
     private TestImsSmsDispatcher mImsSmsDispatcher;
@@ -165,6 +180,8 @@
     private TestSmsDispatcher mCdmaSmsDispatcher;
     private SmsDomainSelectionConnection mSmsDsc;
     private EmergencySmsDomainSelectionConnection mEmergencySmsDsc;
+    private EmergencyStateTracker mEmergencyStateTracker;
+    private CompletableFuture<Integer> mEmergencySmsFuture;
 
     private TestSmsDispatchersController mSmsDispatchersController;
     private boolean mInjectionCallbackTriggered = false;
@@ -174,10 +191,10 @@
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mTracker = mock(SMSDispatcher.SmsTracker.class);
+        mFeatureFlags = mock(FeatureFlags.class);
         setupMockPackagePermissionChecks();
         mSmsDispatchersController = new TestSmsDispatchersController(mPhone, mSmsStorageMonitor,
-            mSmsUsageMonitor, mTestableLooper.getLooper());
-        setUpDomainSelectionConnectionAsNotSupported();
+            mSmsUsageMonitor, mTestableLooper.getLooper(), mFeatureFlags);
         processAllMessages();
     }
 
@@ -191,6 +208,7 @@
         mDscFuture = null;
         mSmsDispatchersController.dispose();
         mSmsDispatchersController = null;
+        mFeatureFlags = null;
         super.tearDown();
     }
 
@@ -436,9 +454,11 @@
     public void testSendEmergencyTextWhenDomainPs() throws Exception {
         setUpDomainSelectionConnection();
         setUpSmsDispatchers();
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
         mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
                 "test-app", false, 0, false, 10, false, 1L, false);
+        processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
                 mSmsDispatchersController.testGetDomainSelectionConnectionHolder(true);
@@ -463,12 +483,92 @@
 
     @Test
     @SmallTest
-    public void testNotifyDomainSelectionTerminated() throws Exception {
+    public void testSendEmergencyTextWhenEmergencyStateTrackerReturnsFailure() throws Exception {
         setUpDomainSelectionConnection();
         setUpSmsDispatchers();
+        setUpEmergencyStateTracker(DisconnectCause.OUT_OF_SERVICE);
+
+        mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
+                "test-app", false, 0, false, 10, false, 1L, false);
+        processAllMessages();
+
+        // Verify the domain selection requested regardless of the result of EmergencyStateTracker.
+        verify(mEmergencySmsDsc).requestDomainSelection(any(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsSentToEmergencyStateTracker() throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L);
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("911"));
+        verify(mEmergencyStateTracker).endSms(eq("1"), eq(true));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsSentToEmergencyStateTrackerWithNonEmergencyNumber() throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("1234", 1L);
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
+        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean());
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsSentToEmergencyStateTrackerWithoutEmergencyStateTracker()
+            throws Exception {
+        setUpDomainSelectionEnabled(true);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L);
+
+        verify(mTelephonyManager, never()).isEmergencyNumber(anyString());
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsSentFailedToEmergencyStateTracker() throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("911", 1L);
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("911"));
+        verify(mEmergencyStateTracker).endSms(eq("1"), eq(false));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsSentFailedToEmergencyStateTrackerWithNonEmergencyNumber()
+            throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("1234", 1L);
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
+        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean());
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyDomainSelectionTerminatedWhenImsAvailableAndNormalSms() throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+        when(mImsSmsDispatcher.isAvailable()).thenReturn(true);
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
                 "test-app", false, 0, false, 10, false, 1L, false);
+        processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
                 mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
@@ -483,10 +583,7 @@
 
         DomainSelectionConnection.DomainSelectionConnectionCallback callback = captor.getValue();
         assertNotNull(callback);
-
-        mSmsDispatchersController.post(() -> {
-            callback.onSelectionTerminated(DisconnectCause.LOCAL);
-        });
+        callback.onSelectionTerminated(DisconnectCause.LOCAL);
         processAllMessages();
 
         verify(mSmsDsc, never()).finishSelection();
@@ -494,13 +591,49 @@
         assertFalse(holder.isDomainSelectionRequested());
         assertEquals(0, holder.getPendingRequests().size());
 
-        // We can use the IntentReceiver for receiving the sent result, but it can be reported as
-        // a flaky test since sometimes broadcasts can take a long time if the system is under load.
-        // At this point, we couldn't use the PendingIntent as a mock because it's a final class
-        // so this test checks the method in the IActivityManager when the PendingIntent#send(int)
-        // is called.
-        verify(mIActivityManager).sendIntentSender(any(), any(), any(),
-                eq(SmsManager.RESULT_ERROR_GENERIC_FAILURE), any(), any(), any(), any(), any());
+        verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
+                any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
+                eq(1L), eq(false));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyDomainSelectionTerminatedWhenImsNotAvailableAndEmergencySms()
+            throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+        when(mImsSmsDispatcher.isAvailable()).thenReturn(false);
+        when(mImsSmsDispatcher.isEmergencySmsSupport(anyString())).thenReturn(true);
+
+        mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
+                "test-app", false, 0, false, 10, false, 1L, false);
+        processAllMessages();
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(true);
+        ArgumentCaptor<DomainSelectionConnection.DomainSelectionConnectionCallback> captor =
+                ArgumentCaptor.forClass(
+                        DomainSelectionConnection.DomainSelectionConnectionCallback.class);
+        verify(mEmergencySmsDsc).requestDomainSelection(any(), captor.capture());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        DomainSelectionConnection.DomainSelectionConnectionCallback callback = captor.getValue();
+        assertNotNull(callback);
+        callback.onSelectionTerminated(DisconnectCause.LOCAL);
+        processAllMessages();
+
+        verify(mEmergencySmsDsc, never()).finishSelection();
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+
+        verify(mImsSmsDispatcher).sendText(eq("911"), eq("2222"), eq("text"), eq(mSentIntent),
+                any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
+                eq(1L), eq(false));
     }
 
     @Test
@@ -511,6 +644,7 @@
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
                 "test-app", false, 0, false, 10, false, 1L, false);
+        processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
                 mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
@@ -521,6 +655,7 @@
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
                 "test-app", false, 0, false, 10, false, 1L, false);
+        processAllMessages();
 
         verify(mSmsDsc).requestDomainSelection(any(), any());
         assertNotNull(holder.getConnection());
@@ -539,6 +674,23 @@
         assertEquals(0, holder.getPendingRequests().size());
     }
 
+    @Test
+    @SmallTest
+    public void testSendTextWhenFeatureFlagDisabledForSmsDomainSelection() throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+        when(mFeatureFlags.smsDomainSelectionEnabled()).thenReturn(false);
+
+        mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
+                "test-app", false, 0, false, 10, false, 1L, false);
+        processAllMessages();
+
+        // Expect that the domain selection logic will not be executed.
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
+        assertNull(holder);
+    }
+
     private void switchImsSmsFormat(int phoneType) {
         mSimulatedCommands.setImsRegistrationState(new int[]{1, phoneType});
         mSimulatedCommands.notifyImsNetworkStateChanged();
@@ -553,7 +705,7 @@
         assertTrue(mSmsDispatchersController.setImsManager(imsManager));
     }
 
-    private void setUpDomainSelectionConnectionAsNotSupported() {
+    private void setUpDomainSelectionEnabled(boolean enabled) {
         mSmsDispatchersController.setDomainSelectionResolverProxy(
                 new SmsDispatchersController.DomainSelectionResolverProxy() {
                     @Override
@@ -566,9 +718,10 @@
 
                     @Override
                     public boolean isDomainSelectionSupported() {
-                        return false;
+                        return true;
                     }
                 });
+        when(mFeatureFlags.smsDomainSelectionEnabled()).thenReturn(enabled);
     }
 
     private void setUpDomainSelectionConnection()  {
@@ -589,6 +742,7 @@
                         return true;
                     }
                 });
+        when(mFeatureFlags.smsDomainSelectionEnabled()).thenReturn(true);
 
         mDscFuture = new CompletableFuture<>();
         when(mSmsDsc.requestDomainSelection(
@@ -620,6 +774,18 @@
                         | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT);
     }
 
+    private void setUpEmergencyStateTracker(int result) throws Exception {
+        mEmergencySmsFuture = new CompletableFuture<Integer>();
+        mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
+        replaceInstance(SmsDispatchersController.class, "mEmergencyStateTracker",
+                mSmsDispatchersController, mEmergencyStateTracker);
+        when(mEmergencyStateTracker.startEmergencySms(any(Phone.class), anyString(), anyBoolean()))
+                .thenReturn(mEmergencySmsFuture);
+        doNothing().when(mEmergencyStateTracker).endSms(anyString(), anyBoolean());
+        mEmergencySmsFuture.complete(result);
+        when(mTelephonyManager.isEmergencyNumber(eq("911"))).thenReturn(true);
+    }
+
     private void sendDataWithDomainSelection(@NetworkRegistrationInfo.Domain int domain,
             boolean isCdmaMo) throws Exception {
         setUpDomainSelectionConnection();
@@ -628,6 +794,7 @@
         byte[] data = new byte[] { 0x01 };
         mSmsDispatchersController.testSendData(
                 "test-app", "1111", "2222", 8080, data, mSentIntent, null, false);
+        processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
                 mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
@@ -663,6 +830,7 @@
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
                 "test-app", false, 0, false, 10, false, 1L, false);
+        processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
                 mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
@@ -704,6 +872,7 @@
         ArrayList<PendingIntent> deliveryIntents = new ArrayList<>();
         mSmsDispatchersController.testSendMultipartText("1111", "2222", parts, sentIntents,
                 deliveryIntents, null, "test-app", false, 0, false, 10, 1L);
+        processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
                 mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
@@ -746,6 +915,7 @@
         replaceInstance(SMSDispatcher.SmsTracker.class, "mFormat", mTracker, smsFormat);
 
         mSmsDispatchersController.sendRetrySms(mTracker);
+        processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
                 mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
@@ -782,6 +952,7 @@
         mTracker.mUsesImsServiceForIms = true;
 
         mSmsDispatchersController.sendRetrySms(mTracker);
+        processAllMessages();
 
         verify(mSmsDsc, never()).requestDomainSelection(any(), any());
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
index bd963ef..dc1ee63 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
@@ -18,7 +18,8 @@
 
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.telephony.Rlog;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsNumberUtilsTest.java
index 3c1cec1..90cc0fe 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsNumberUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsNumberUtilsTest.java
@@ -26,7 +26,8 @@
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
index b6775eb..4483c61 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
@@ -27,11 +27,12 @@
 import android.content.res.Resources;
 import android.os.Message;
 import android.provider.Telephony;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -39,7 +40,6 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
-
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class SmsStorageMonitorTest extends TelephonyTest {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
index 3bafe4d..ac92b8f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
@@ -17,18 +17,29 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
 import android.os.Parcel;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 
+import com.android.internal.telephony.flags.Flags;
+
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
+import java.util.Set;
+
 public class SubscriptionInfoTest {
     private SubscriptionInfo mSubscriptionInfoUT;
-    private static final String[] EHPLMNS = new String[] {"310999", "310998"};
-    private static final String[] HPLMNS = new String[] {"310001"};
+    private static final String[] EHPLMNS = new String[]{"310999", "310998"};
+    private static final String[] HPLMNS = new String[]{"310001"};
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @After
     public void tearDown() throws Exception {
@@ -37,6 +48,8 @@
 
     @Before
     public void setUp() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG);
         mSubscriptionInfoUT = new SubscriptionInfo.Builder()
                 .setId(1)
                 .setIccId("890126042XXXXXXXXXXX")
@@ -52,6 +65,10 @@
                 .setEhplmns(EHPLMNS)
                 .setHplmns(HPLMNS)
                 .setCountryIso("us")
+                .setOnlyNonTerrestrialNetwork(true)
+                .setServiceCapabilities(SubscriptionManager.getServiceCapabilitiesSet(
+                    SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK))
+                .setTransferStatus(1)
                 .build();
     }
 
@@ -71,6 +88,14 @@
         assertThat(mSubscriptionInfoUT.getSubscriptionId()).isEqualTo(1);
         assertThat(mSubscriptionInfoUT.getSimSlotIndex()).isEqualTo(0);
         assertThat(mSubscriptionInfoUT.getIccId()).isEqualTo("890126042XXXXXXXXXXX");
+        if (Flags.oemEnabledSatelliteFlag()) {
+            assertThat(mSubscriptionInfoUT.isOnlyNonTerrestrialNetwork()).isTrue();
+        }
+        assertThat(mSubscriptionInfoUT.getServiceCapabilities()).isEqualTo(
+                Set.of(SubscriptionManager.SERVICE_CAPABILITY_DATA));
+        if (Flags.supportPsimToEsimConversion()) {
+            assertThat(mSubscriptionInfoUT.getTransferStatus()).isEqualTo(1);
+        }
     }
 
     @Test
@@ -95,5 +120,32 @@
         assertThat(mSubscriptionInfoUT).isEqualTo(copiedInfo);
         assertThat(mSubscriptionInfoUT).isNotEqualTo(differentDisplayName);
         assertThat(mSubscriptionInfoUT).isNotEqualTo(differentSubId);
+
+        SubscriptionInfo differentServiceCapabilities =
+                new SubscriptionInfo.Builder(mSubscriptionInfoUT)
+                        .setServiceCapabilities(SubscriptionManager.getServiceCapabilitiesSet(
+                                SubscriptionManager.SERVICE_CAPABILITY_SMS_BITMASK))
+                        .build();
+        assertThat(mSubscriptionInfoUT).isNotEqualTo(differentServiceCapabilities);
+    }
+
+    @Test
+    public void testInvalidServiceCapability_tooLarge() {
+        assertThrows("IllegalArgumentException should throw when set invalid service capability.",
+                IllegalArgumentException.class,
+                () -> new SubscriptionInfo.Builder()
+                        .setServiceCapabilities(
+                                Set.of(SubscriptionManager.SERVICE_CAPABILITY_MAX + 1))
+                        .build());
+    }
+
+    @Test
+    public void testInvalidServiceCapability_tooSmall() {
+        assertThrows("IllegalArgumentException should throw when set invalid service capability.",
+                IllegalArgumentException.class,
+                () -> new SubscriptionInfo.Builder()
+                        .setServiceCapabilities(
+                                Set.of(SubscriptionManager.SERVICE_CAPABILITY_VOICE - 1))
+                        .build());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java
new file mode 100644
index 0000000..a2763fe
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2023 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.location.Geocoder;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.testing.TestableLooper;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+/** Test for {@link TelephonyCountryDetector} */
+@RunWith(AndroidJUnit4.class)
+public class TelephonyCountryDetectorTest extends TelephonyTest {
+    private static final String TAG = "TelephonyCountryDetectorTest";
+
+    @Mock
+    ServiceStateTracker mSST2;
+    @Mock
+    LocaleTracker mMockLocaleTracker;
+    @Mock
+    LocaleTracker mMockLocaleTracker2;
+    @Mock Location mMockLocation;
+    @Mock Network mMockNetwork;
+
+    @Captor
+    private ArgumentCaptor<LocationListener> mLocationListenerCaptor;
+    @Captor
+    private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor;
+
+    private LocaleTracker[] mLocaleTrackers;
+    private Looper mLooper;
+    private NetworkCapabilities mNetworkCapabilities;
+    private TestTelephonyCountryDetector mCountryDetectorUT;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+
+        HandlerThread handlerThread = new HandlerThread("CountryDetectorTest");
+        handlerThread.start();
+        mLooper = handlerThread.getLooper();
+        mTestableLooper = new TestableLooper(mLooper);
+
+        mLocaleTrackers = new LocaleTracker[]{mMockLocaleTracker, mMockLocaleTracker2};
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2});
+        when(mPhone.getServiceStateTracker()).thenReturn(mSST);
+        when(mPhone.getPhoneId()).thenReturn(0);
+        when(mSST.getLocaleTracker()).thenReturn(mMockLocaleTracker);
+        when(mMockLocaleTracker.getCurrentCountry()).thenReturn("");
+        when(mPhone2.getServiceStateTracker()).thenReturn(mSST2);
+        when(mPhone2.getPhoneId()).thenReturn(1);
+        when(mSST2.getLocaleTracker()).thenReturn(mMockLocaleTracker2);
+        when(mMockLocaleTracker2.getCurrentCountry()).thenReturn("");
+
+        when(mConnectivityManager.getActiveNetwork()).thenReturn(mMockNetwork);
+        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, true);
+        when(mConnectivityManager.getNetworkCapabilities(any(Network.class)))
+                .thenReturn(mNetworkCapabilities);
+
+        when(mLocationManager.getProviders(true)).thenReturn(Arrays.asList("TEST_PROVIDER"));
+
+        mCountryDetectorUT = new TestTelephonyCountryDetector(
+                mLooper, mContext, mLocationManager, mConnectivityManager);
+        if (isGeoCoderImplemented()) {
+            verify(mLocationManager).requestLocationUpdates(anyString(), anyLong(), anyFloat(),
+                    mLocationListenerCaptor.capture());
+            verify(mLocationManager).getProviders(true);
+            verify(mLocationManager).getLastKnownLocation(anyString());
+        }
+        verify(mConnectivityManager).registerNetworkCallback(
+                any(NetworkRequest.class), mNetworkCallbackCaptor.capture());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+        Log.d(TAG, "tearDown");
+    }
+
+    @Test
+    public void testGetInstance() {
+        clearInvocations(mLocationManager);
+        clearInvocations(mConnectivityManager);
+        when(mMockLocaleTracker.getCurrentCountry()).thenReturn("US");
+        TelephonyCountryDetector inst1 = TelephonyCountryDetector.getInstance(mContext);
+        TelephonyCountryDetector inst2 = TelephonyCountryDetector.getInstance(mContext);
+        assertEquals(inst1, inst2);
+        if (isGeoCoderImplemented()) {
+            verify(mLocationManager, never()).requestLocationUpdates(anyString(), anyLong(),
+                    anyFloat(), any(LocationListener.class));
+        }
+        verify(mConnectivityManager).registerNetworkCallback(
+                any(NetworkRequest.class), any(ConnectivityManager.NetworkCallback.class));
+    }
+
+    @Test
+    public void testGetCurrentNetworkCountryIso() {
+        // No ServiceStateTracker
+        when(mPhone.getServiceStateTracker()).thenReturn(null);
+        when(mPhone2.getServiceStateTracker()).thenReturn(null);
+        assertTrue(mCountryDetectorUT.getCurrentNetworkCountryIso().isEmpty());
+
+        // No LocaleTracker
+        when(mPhone.getServiceStateTracker()).thenReturn(mSST);
+        when(mSST.getLocaleTracker()).thenReturn(null);
+        when(mPhone2.getServiceStateTracker()).thenReturn(mSST2);
+        when(mSST2.getLocaleTracker()).thenReturn(null);
+        assertTrue(mCountryDetectorUT.getCurrentNetworkCountryIso().isEmpty());
+
+        // LocaleTracker returns invalid country
+        when(mSST.getLocaleTracker()).thenReturn(mMockLocaleTracker);
+        when(mMockLocaleTracker.getCurrentCountry()).thenReturn("1234");
+        when(mSST2.getLocaleTracker()).thenReturn(mMockLocaleTracker2);
+        when(mMockLocaleTracker2.getCurrentCountry()).thenReturn("");
+        assertTrue(mCountryDetectorUT.getCurrentNetworkCountryIso().isEmpty());
+
+        // LocaleTracker of phone 2 returns valid country
+        when(mMockLocaleTracker2.getCurrentCountry()).thenReturn("US");
+        assertEquals(1, mCountryDetectorUT.getCurrentNetworkCountryIso().size());
+        assertTrue(mCountryDetectorUT.getCurrentNetworkCountryIso().contains("US"));
+
+        // Phone 1 is also in US
+        when(mMockLocaleTracker.getCurrentCountry()).thenReturn("US");
+        assertEquals(1, mCountryDetectorUT.getCurrentNetworkCountryIso().size());
+        assertTrue(mCountryDetectorUT.getCurrentNetworkCountryIso().contains("US"));
+
+        // Phone 1 is in US and Phone 2 is in CA
+        when(mMockLocaleTracker.getCurrentCountry()).thenReturn("CA");
+        assertEquals(2, mCountryDetectorUT.getCurrentNetworkCountryIso().size());
+        assertTrue(mCountryDetectorUT.getCurrentNetworkCountryIso().contains("US"));
+        assertTrue(mCountryDetectorUT.getCurrentNetworkCountryIso().contains("CA"));
+    }
+
+    @Test
+    public void testCachedNetworkCountryCodeUpdate() {
+        assertTrue(mCountryDetectorUT.getCachedNetworkCountryIsoInfo().isEmpty());
+
+        // Update network country code of Phone 1
+        sendNetworkCountryCodeChanged("US", mPhone);
+        assertEquals(1, mCountryDetectorUT.getCachedNetworkCountryIsoInfo().size());
+        assertTrue(mCountryDetectorUT.getCachedNetworkCountryIsoInfo().containsKey("US"));
+        assertEquals(0, (long) mCountryDetectorUT.getCachedNetworkCountryIsoInfo().get("US"));
+
+        // Move time forwards and update network country code of Phone 2
+        mCountryDetectorUT.elapsedRealtimeNanos = 2;
+        sendNetworkCountryCodeChanged("US", mPhone2);
+        assertEquals(1, mCountryDetectorUT.getCachedNetworkCountryIsoInfo().size());
+        assertTrue(mCountryDetectorUT.getCachedNetworkCountryIsoInfo().containsKey("US"));
+        assertEquals(2, (long) mCountryDetectorUT.getCachedNetworkCountryIsoInfo().get("US"));
+
+        // Move time forwards and update network country code of Phone 1
+        mCountryDetectorUT.elapsedRealtimeNanos = 3;
+        sendNetworkCountryCodeChanged("CA", mPhone);
+        assertEquals(2, mCountryDetectorUT.getCachedNetworkCountryIsoInfo().size());
+        assertTrue(mCountryDetectorUT.getCachedNetworkCountryIsoInfo().containsKey("US"));
+        assertEquals(2, (long) mCountryDetectorUT.getCachedNetworkCountryIsoInfo().get("US"));
+        assertTrue(mCountryDetectorUT.getCachedNetworkCountryIsoInfo().containsKey("CA"));
+        assertEquals(3, (long) mCountryDetectorUT.getCachedNetworkCountryIsoInfo().get("CA"));
+
+        // Move time forwards and update network country code of Phone 2
+        mCountryDetectorUT.elapsedRealtimeNanos = 4;
+        sendNetworkCountryCodeChanged("CA", mPhone2);
+        assertEquals(1, mCountryDetectorUT.getCachedNetworkCountryIsoInfo().size());
+        assertTrue(mCountryDetectorUT.getCachedNetworkCountryIsoInfo().containsKey("CA"));
+        assertEquals(4, (long) mCountryDetectorUT.getCachedNetworkCountryIsoInfo().get("CA"));
+
+        // Move time forwards and update network country code of Phone 1
+        mCountryDetectorUT.elapsedRealtimeNanos = 5;
+        sendNetworkCountryCodeChanged("", mPhone);
+        assertEquals(1, mCountryDetectorUT.getCachedNetworkCountryIsoInfo().size());
+        assertTrue(mCountryDetectorUT.getCachedNetworkCountryIsoInfo().containsKey("CA"));
+        assertEquals(5, (long) mCountryDetectorUT.getCachedNetworkCountryIsoInfo().get("CA"));
+
+        // Move time forwards and update network country code of Phone 2
+        mCountryDetectorUT.elapsedRealtimeNanos = 6;
+        sendNetworkCountryCodeChanged("", mPhone2);
+        assertEquals(1, mCountryDetectorUT.getCachedNetworkCountryIsoInfo().size());
+        assertTrue(mCountryDetectorUT.getCachedNetworkCountryIsoInfo().containsKey("CA"));
+        assertEquals(6, (long) mCountryDetectorUT.getCachedNetworkCountryIsoInfo().get("CA"));
+    }
+
+    @Test
+    public void testCachedLocationCountryCodeUpdate() {
+        if (!isGeoCoderImplemented()) {
+            logd("Skip the test because GeoCoder is not implemented on the device");
+            return;
+        }
+        assertNull(mCountryDetectorUT.getCachedLocationCountryIsoInfo().first);
+        assertEquals(0, (long) mCountryDetectorUT.getCachedLocationCountryIsoInfo().second);
+        assertFalse(mCountryDetectorUT.queryCountryCodeForLocationTriggered);
+
+        // Move time forwards and update location-based country code
+        mCountryDetectorUT.elapsedRealtimeNanos = 2;
+        mCountryDetectorUT.queryCountryCodeForLocationTriggered = false;
+        sendLocationUpdate();
+        assertTrue(mCountryDetectorUT.queryCountryCodeForLocationTriggered);
+        sendLocationBasedCountryCodeChanged("CA", 1);
+        mTestableLooper.processAllMessages();
+        assertEquals("CA", mCountryDetectorUT.getCachedLocationCountryIsoInfo().first);
+        assertEquals(1, (long) mCountryDetectorUT.getCachedLocationCountryIsoInfo().second);
+    }
+
+    @Test
+    public void testRegisterForLocationUpdates() {
+        if (!isGeoCoderImplemented()) {
+            logd("Skip the test because GeoCoder is not implemented on the device");
+            return;
+        }
+
+        // Network country code is available
+        clearInvocations(mLocationManager);
+        sendNetworkCountryCodeChanged("US", mPhone);
+        verify(mLocationManager).removeUpdates(any(LocationListener.class));
+
+        // Network country code is not available and Wi-fi is available
+        clearInvocations(mLocationManager);
+        sendNetworkCountryCodeChanged("", mPhone);
+        verify(mLocationManager).requestLocationUpdates(anyString(), anyLong(), anyFloat(),
+                any(LocationListener.class));
+
+        // Wi-fi is not available
+        clearInvocations(mLocationManager);
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, false);
+        mNetworkCallbackCaptor.getValue().onLost(mMockNetwork);
+        mTestableLooper.processAllMessages();
+        verify(mLocationManager, never()).removeUpdates(any(LocationListener.class));
+
+        // Wi-fi becomes available
+        clearInvocations(mLocationManager);
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
+        mNetworkCallbackCaptor.getValue().onAvailable(mMockNetwork);
+        mTestableLooper.processAllMessages();
+        // Location updates were already requested
+        verify(mLocationManager, never()).requestLocationUpdates(anyString(), anyLong(), anyFloat(),
+                any(LocationListener.class));
+
+        // Make Wi-fi not available and reset the quota
+        clearInvocations(mLocationManager);
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, false);
+        mTestableLooper.moveTimeForward(
+                TestTelephonyCountryDetector.getLocationUpdateRequestQuotaResetTimeoutMillis());
+        mTestableLooper.processAllMessages();
+        verify(mLocationManager).removeUpdates(any(LocationListener.class));
+
+        // Wi-fi becomes available
+        clearInvocations(mLocationManager);
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
+        mNetworkCallbackCaptor.getValue().onAvailable(mMockNetwork);
+        mTestableLooper.processAllMessages();
+        verify(mLocationManager).requestLocationUpdates(anyString(), anyLong(), anyFloat(),
+                any(LocationListener.class));
+
+        // Reset the quota
+        clearInvocations(mLocationManager);
+        mTestableLooper.moveTimeForward(
+                TestTelephonyCountryDetector.getLocationUpdateRequestQuotaResetTimeoutMillis());
+        mTestableLooper.processAllMessages();
+        verify(mLocationManager, never()).removeUpdates(any(LocationListener.class));
+        verify(mLocationManager, never()).requestLocationUpdates(anyString(), anyLong(), anyFloat(),
+                any(LocationListener.class));
+
+        // Wi-fi becomes not available
+        clearInvocations(mLocationManager);
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, false);
+        mNetworkCallbackCaptor.getValue().onUnavailable();
+        mTestableLooper.processAllMessages();
+        verify(mLocationManager).removeUpdates(any(LocationListener.class));
+    }
+
+    private static boolean isGeoCoderImplemented() {
+        return Geocoder.isPresent();
+    }
+
+    private void sendLocationUpdate() {
+        mLocationListenerCaptor.getValue().onLocationChanged(mMockLocation);
+        mTestableLooper.processAllMessages();
+    }
+
+    private void sendLocationBasedCountryCodeChanged(String countryCode, long locationUpdatedTime) {
+        Message message = mCountryDetectorUT.obtainMessage(
+                2 /* EVENT_LOCATION_COUNTRY_CODE_CHANGED */,
+                new Pair<>(countryCode, locationUpdatedTime));
+        message.sendToTarget();
+        mTestableLooper.processAllMessages();
+    }
+
+    private void sendNetworkCountryCodeChanged(String countryCode, @NonNull Phone phone) {
+        when(mLocaleTrackers[phone.getPhoneId()].getCurrentCountry()).thenReturn(countryCode);
+        mCountryDetectorUT.onNetworkCountryCodeChanged(phone, countryCode);
+        mTestableLooper.processAllMessages();
+    }
+
+    private static class TestTelephonyCountryDetector extends TelephonyCountryDetector {
+        public boolean queryCountryCodeForLocationTriggered = false;
+        public long elapsedRealtimeNanos = 0;
+
+        /**
+         * Create the singleton instance of {@link TelephonyCountryDetector}.
+         *
+         * @param looper           The looper to run the {@link TelephonyCountryDetector} instance.
+         * @param context          The context associated with the instance.
+         * @param locationManager  The LocationManager instance.
+         */
+        TestTelephonyCountryDetector(Looper looper, Context context,
+                LocationManager locationManager, ConnectivityManager connectivityManager) {
+            super(looper, context, locationManager, connectivityManager);
+        }
+
+        @Override
+        protected void queryCountryCodeForLocation(@NonNull Location location) {
+            queryCountryCodeForLocationTriggered = true;
+        }
+
+        @Override
+        protected long getElapsedRealtimeNanos() {
+            return elapsedRealtimeNanos;
+        }
+
+        public static long getLocationUpdateRequestQuotaResetTimeoutMillis() {
+            return WAIT_FOR_LOCATION_UPDATE_REQUEST_QUOTA_RESET_TIMEOUT_MILLIS;
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyHistogramTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyHistogramTest.java
index c65ce91..3111e7b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyHistogramTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyHistogramTest.java
@@ -15,15 +15,16 @@
  */
 package com.android.internal.telephony;
 
+import static org.junit.Assert.*;
+
 import android.telephony.TelephonyHistogram;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.Assert;
-
-import static org.junit.Assert.*;
 
 public class TelephonyHistogramTest {
     private TelephonyHistogram mHistogram = null;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
index a053c56..6369825 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -46,8 +46,10 @@
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.pm.permission.LegacyPermissionManagerService;
 
@@ -71,6 +73,7 @@
 
     // Mocked classes
     private Context mMockContext;
+    private FeatureFlags mMockFeatureFlag;
     private AppOpsManager mMockAppOps;
     private SubscriptionManager mMockSubscriptionManager;
     private ITelephony mMockTelephony;
@@ -84,10 +87,12 @@
 
     private MockContentResolver mMockContentResolver;
     private FakeSettingsConfigProvider mFakeSettingsConfigProvider;
+    private FeatureFlags mRealFeatureFlagToBeRestored;
 
     @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);
@@ -129,13 +134,17 @@
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
         when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                 PID, UID)).thenReturn(PackageManager.PERMISSION_DENIED);
+
+        replaceFeatureFlag(mMockFeatureFlag);
         setTelephonyMockAsService();
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
+        replaceFeatureFlag(mRealFeatureFlagToBeRestored);
         mMockContentResolver = null;
         mFakeSettingsConfigProvider = null;
+        mRealFeatureFlagToBeRestored = null;
     }
 
     @Test
@@ -161,8 +170,10 @@
     public void testCheckReadPhoneState_hasPermissionAndAppOp() {
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PHONE_STATE, PID, UID, MSG);
-        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), eq(UID), eq(PACKAGE),
-                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockAppOps
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), eq(UID), eq(PACKAGE),
+                        eq(FEATURE), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
         assertTrue(TelephonyPermissions.checkReadPhoneState(
                 mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
     }
@@ -203,8 +214,10 @@
     public void testCheckReadPhoneStateOnAnyActiveSub_hasPermissionAndAppOp() {
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PHONE_STATE, PID, UID, MSG);
-        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), eq(UID), eq(PACKAGE),
-                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockAppOps
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), eq(UID), eq(PACKAGE),
+                        eq(FEATURE), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
         assertTrue(TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
                 mMockContext, PID, UID, PACKAGE, FEATURE, MSG));
     }
@@ -540,6 +553,30 @@
                 UserHandle.SYSTEM, "911"));
     }
 
+    @Test
+    public void testCheckSubscriptionAssociatedWithUser_badSub_flag_enabled() {
+        doReturn(true).when(mMockFeatureFlag).rejectBadSubIdInteraction();
+
+        doThrow(new IllegalArgumentException("has no records on device"))
+                .when(mMockSubscriptionManager).isSubscriptionAssociatedWithUser(SUB_ID,
+                        UserHandle.SYSTEM);
+        assertFalse(TelephonyPermissions.checkSubscriptionAssociatedWithUser(mMockContext, SUB_ID,
+                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);
@@ -630,4 +667,12 @@
         field.set(providerHolder, iContentProvider);
     }
 
+    private synchronized void replaceFeatureFlag(final FeatureFlags newValue)
+            throws Exception {
+        Field field = TelephonyPermissions.class.getDeclaredField("sFeatureFlag");
+        field.setAccessible(true);
+
+        mRealFeatureFlagToBeRestored = (FeatureFlags) field.get(null);
+        field.set(null, newValue);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index 35a3186..6050b18 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -66,13 +66,14 @@
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
 
 import com.android.server.TelephonyRegistry;
 
@@ -108,12 +109,14 @@
     private int mRadioPowerState = RADIO_POWER_UNAVAILABLE;
     private int mDataConnectionState = TelephonyManager.DATA_UNKNOWN;
     private int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    private int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
     private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
     private CellLocation mCellLocation;
     private List<CellInfo> mCellInfo;
     private BarringInfo mBarringInfo = null;
     private CellIdentity mCellIdentityForRegiFail;
     private int mRegistrationFailReason;
+    private Set<Integer> mSimultaneousCallingSubscriptions;
 
     // All events contribute to TelephonyRegistry#isPhoneStatePermissionRequired
     private static final Set<Integer> READ_PHONE_STATE_EVENTS;
@@ -159,6 +162,8 @@
                 TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED);
         READ_PRIVILEGED_PHONE_STATE_EVENTS.add(
                 TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
+        READ_PRIVILEGED_PHONE_STATE_EVENTS.add(
+                TelephonyCallback.EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
     }
 
     // All events contribute to TelephonyRegistry#isActiveEmergencySessionPermissionRequired
@@ -185,7 +190,9 @@
             TelephonyCallback.ServiceStateListener,
             TelephonyCallback.CellInfoListener,
             TelephonyCallback.BarringInfoListener,
-            TelephonyCallback.RegistrationFailedListener {
+            TelephonyCallback.RegistrationFailedListener,
+            TelephonyCallback.DataActivityListener,
+            TelephonyCallback.SimultaneousCellularCallingSupportListener {
         // This class isn't mockable to get invocation counts because the IBinder is null and
         // crashes the TelephonyRegistry. Make a cheesy verify(times()) alternative.
         public AtomicInteger invocationCount = new AtomicInteger(0);
@@ -268,6 +275,18 @@
             mCellIdentityForRegiFail = cellIdentity;
             mRegistrationFailReason = causeCode;
         }
+
+        public void onDataActivity(@Annotation.DataActivityType int direction) {
+            invocationCount.incrementAndGet();
+            mDataActivity = direction;
+        }
+
+        @Override
+        public void onSimultaneousCellularCallingSubscriptionsChanged(
+                @NonNull Set<Integer> simultaneousCallingSubscriptionIds) {
+            invocationCount.incrementAndGet();
+            mSimultaneousCallingSubscriptions = simultaneousCallingSubscriptionIds;
+        }
     }
 
     private void addTelephonyRegistryService() {
@@ -1490,4 +1509,57 @@
         assertEquals(2, mTelephonyCallback.invocationCount.get());
         assertEquals(mCellInfo, dummyCellInfo);
     }
+
+    @Test
+    public void testNotifyDataActivityForSubscriberWithSlot() {
+        final int subId = 1;
+        int[] events = {TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED};
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
+
+        assertEquals(TelephonyManager.DATA_ACTIVITY_NONE, mDataActivity);
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+
+        mTelephonyRegistry.notifyDataActivityForSubscriberWithSlot(0/*phoneId*/, subId,
+                TelephonyManager.DATA_ACTIVITY_INOUT);
+        processAllMessages();
+        assertEquals(TelephonyManager.DATA_ACTIVITY_INOUT, mDataActivity);
+    }
+
+    @Test
+    public void testNotifyDataActivityForSubscriberWithSlotForInvalidSubId() {
+        final int subId = INVALID_SUBSCRIPTION_ID;
+        int[] events = {TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED};
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
+
+        assertEquals(TelephonyManager.DATA_ACTIVITY_NONE, mDataActivity);
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+
+        mTelephonyRegistry.notifyDataActivityForSubscriberWithSlot(0/*phoneId*/, subId,
+                TelephonyManager.DATA_ACTIVITY_OUT);
+        processAllMessages();
+        assertEquals(TelephonyManager.DATA_ACTIVITY_OUT, mDataActivity);
+    }
+
+    @Test
+    public void testSimultaneousCellularCallingSubscriptionsChanged() {
+        final int subId = INVALID_SUBSCRIPTION_ID;
+        int[] events = {TelephonyCallback
+                .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED};
+
+        int[] subIds = {0, 1, 2};
+        Set<Integer> subIdSet = new ArraySet<>(3);
+        for (Integer s : subIds) {
+            subIdSet.add(s);
+        }
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+
+        mTelephonyRegistry.notifySimultaneousCellularCallingSubscriptionsChanged(subIds);
+        processAllMessages();
+        assertEquals(subIdSet, mSimultaneousCallingSubscriptions);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 705bafd..a6e06e3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -64,6 +64,7 @@
 import android.os.RegistrantList;
 import android.os.ServiceManager;
 import android.os.StrictMode;
+import android.os.SystemClock;
 import android.os.UserManager;
 import android.permission.LegacyPermissionManager;
 import android.provider.BlockedNumberContract;
@@ -94,6 +95,7 @@
 import com.android.ims.ImsCall;
 import com.android.ims.ImsEcbm;
 import com.android.ims.ImsManager;
+import com.android.internal.telephony.analytics.TelephonyAnalytics;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.cdma.EriManager;
 import com.android.internal.telephony.data.AccessNetworksManager;
@@ -106,11 +108,14 @@
 import com.android.internal.telephony.data.DataSettingsManager;
 import com.android.internal.telephony.data.LinkBandwidthEstimator;
 import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsNrSaModeHandler;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
+import com.android.internal.telephony.metrics.DefaultNetworkMonitor;
 import com.android.internal.telephony.metrics.DeviceStateHelper;
 import com.android.internal.telephony.metrics.ImsStats;
 import com.android.internal.telephony.metrics.MetricsCollector;
@@ -119,6 +124,9 @@
 import com.android.internal.telephony.metrics.SmsStats;
 import com.android.internal.telephony.metrics.VoiceCallSessionStats;
 import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.internal.telephony.security.CellularIdentifierDisclosureNotifier;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource;
+import com.android.internal.telephony.security.NullCipherNotifier;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.test.SimulatedCommandsVerifier;
@@ -146,12 +154,12 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 public abstract class TelephonyTest {
     protected static String TAG;
@@ -182,6 +190,7 @@
     }
 
     // Mocked classes
+    protected FeatureFlags mFeatureFlags;
     protected GsmCdmaPhone mPhone;
     protected GsmCdmaPhone mPhone2;
     protected ImsPhone mImsPhone;
@@ -246,6 +255,7 @@
     protected IntentBroadcaster mIntentBroadcaster;
     protected NitzStateMachine mNitzStateMachine;
     protected RadioConfig mMockRadioConfig;
+    protected RadioConfigProxy mMockRadioConfigProxy;
     protected LocaleTracker mLocaleTracker;
     protected RestrictedState mRestrictedState;
     protected PhoneConfigurationManager mPhoneConfigurationManager;
@@ -258,8 +268,10 @@
     protected CarrierPrivilegesTracker mCarrierPrivilegesTracker;
     protected VoiceCallSessionStats mVoiceCallSessionStats;
     protected PersistAtomsStorage mPersistAtomsStorage;
+    protected DefaultNetworkMonitor mDefaultNetworkMonitor;
     protected MetricsCollector mMetricsCollector;
     protected SmsStats mSmsStats;
+    protected TelephonyAnalytics mTelephonyAnalytics;
     protected SignalStrength mSignalStrength;
     protected WifiManager mWifiManager;
     protected WifiInfo mWifiInfo;
@@ -274,6 +286,10 @@
     protected ServiceStateStats mServiceStateStats;
     protected SatelliteController mSatelliteController;
     protected DeviceStateHelper mDeviceStateHelper;
+    protected CellularNetworkSecuritySafetySource mSafetySource;
+    protected CellularIdentifierDisclosureNotifier mIdentifierDisclosureNotifier;
+    protected DomainSelectionResolver mDomainSelectionResolver;
+    protected NullCipherNotifier mNullCipherNotifier;
 
     // Initialized classes
     protected ActivityManager mActivityManager;
@@ -296,7 +312,7 @@
     protected Context mContext;
     protected FakeBlockedNumberContentProvider mFakeBlockedNumberContentProvider;
     private final ContentProvider mContentProvider = spy(new ContextFixture.FakeContentProvider());
-    private Object mLock = new Object();
+    private final Object mLock = new Object();
     private boolean mReady;
     protected HashMap<String, IBinder> mServiceManagerMockedServices = new HashMap<>();
     protected Phone[] mPhones;
@@ -310,7 +326,37 @@
 
     private final HashMap<InstanceKey, Object> mOldInstances = new HashMap<>();
 
-    private final LinkedList<InstanceKey> mInstanceKeys = new LinkedList<>();
+    private final List<InstanceKey> mInstanceKeys = new ArrayList<>();
+
+    protected int mIntegerConsumerResult;
+    protected Semaphore mIntegerConsumerSemaphore = new Semaphore(0);
+    protected  Consumer<Integer> mIntegerConsumer = new Consumer<Integer>() {
+        @Override
+        public void accept(Integer integer) {
+            logd("mIIntegerConsumer: result=" + integer);
+            mIntegerConsumerResult =  integer;
+            try {
+                mIntegerConsumerSemaphore.release();
+            } catch (Exception ex) {
+                logd("mIIntegerConsumer: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    protected boolean waitForIntegerConsumerResponse(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mIntegerConsumerSemaphore.tryAcquire(500 /*Timeout*/, TimeUnit.MILLISECONDS)) {
+                    logd("Timeout to receive IIntegerConsumer() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                logd("waitForIIntegerConsumerResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
 
     private class InstanceKey {
         public final Class mClass;
@@ -329,7 +375,7 @@
 
         @Override
         public boolean equals(Object obj) {
-            if (obj == null || obj.getClass() != getClass()) {
+            if (obj == null || !(obj instanceof InstanceKey)) {
                 return false;
             }
 
@@ -341,15 +387,18 @@
 
     protected void waitUntilReady() {
         synchronized (mLock) {
-            if (!mReady) {
+            long now = SystemClock.elapsedRealtime();
+            long deadline = now + MAX_INIT_WAIT_MS;
+            while (!mReady && now < deadline) {
                 try {
                     mLock.wait(MAX_INIT_WAIT_MS);
-                } catch (InterruptedException ie) {
+                } catch (Exception e) {
+                    fail("Telephony tests failed to initialize: e=" + e);
                 }
-
-                if (!mReady) {
-                    fail("Telephony tests failed to initialize");
-                }
+                now = SystemClock.elapsedRealtime();
+            }
+            if (!mReady) {
+                fail("Telephony tests failed to initialize");
             }
         }
     }
@@ -375,6 +424,16 @@
         field.set(obj, newValue);
     }
 
+    protected static <T> T getPrivateField(Object object, String fieldName, Class<T> fieldType)
+            throws Exception {
+
+        Class<?> clazz = object.getClass();
+        Field field = clazz.getDeclaredField(fieldName);
+        field.setAccessible(true);
+
+        return fieldType.cast(field.get(object));
+    }
+
     protected synchronized void restoreInstance(final Class c, final String instanceName,
                                                 final Object obj) throws Exception {
         InstanceKey key = new InstanceKey(c, instanceName, obj);
@@ -388,10 +447,8 @@
     }
 
     protected synchronized void restoreInstances() throws Exception {
-        Iterator<InstanceKey> it = mInstanceKeys.descendingIterator();
-
-        while (it.hasNext()) {
-            InstanceKey key = it.next();
+        for (int i = mInstanceKeys.size() - 1; i >= 0; i--) {
+            InstanceKey key = mInstanceKeys.get(i);
             Field field = key.mClass.getDeclaredField(key.mInstName);
             field.setAccessible(true);
             field.set(key.mObj, mOldInstances.get(key));
@@ -419,6 +476,7 @@
     protected void setUp(String tag) throws Exception {
         TAG = tag;
         enableStrictMode();
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
         mPhone = Mockito.mock(GsmCdmaPhone.class);
         mPhone2 = Mockito.mock(GsmCdmaPhone.class);
         mImsPhone = Mockito.mock(ImsPhone.class);
@@ -483,6 +541,7 @@
         mIntentBroadcaster = Mockito.mock(IntentBroadcaster.class);
         mNitzStateMachine = Mockito.mock(NitzStateMachine.class);
         mMockRadioConfig = Mockito.mock(RadioConfig.class);
+        mMockRadioConfigProxy = Mockito.mock(RadioConfigProxy.class);
         mLocaleTracker = Mockito.mock(LocaleTracker.class);
         mRestrictedState = Mockito.mock(RestrictedState.class);
         mPhoneConfigurationManager = Mockito.mock(PhoneConfigurationManager.class);
@@ -495,8 +554,10 @@
         mCarrierPrivilegesTracker = Mockito.mock(CarrierPrivilegesTracker.class);
         mVoiceCallSessionStats = Mockito.mock(VoiceCallSessionStats.class);
         mPersistAtomsStorage = Mockito.mock(PersistAtomsStorage.class);
+        mDefaultNetworkMonitor = Mockito.mock(DefaultNetworkMonitor.class);
         mMetricsCollector = Mockito.mock(MetricsCollector.class);
         mSmsStats = Mockito.mock(SmsStats.class);
+        mTelephonyAnalytics = Mockito.mock(TelephonyAnalytics.class);
         mSignalStrength = Mockito.mock(SignalStrength.class);
         mWifiManager = Mockito.mock(WifiManager.class);
         mWifiInfo = Mockito.mock(WifiInfo.class);
@@ -511,6 +572,10 @@
         mServiceStateStats = Mockito.mock(ServiceStateStats.class);
         mSatelliteController = Mockito.mock(SatelliteController.class);
         mDeviceStateHelper = Mockito.mock(DeviceStateHelper.class);
+        mSafetySource = Mockito.mock(CellularNetworkSecuritySafetySource.class);
+        mIdentifierDisclosureNotifier = Mockito.mock(CellularIdentifierDisclosureNotifier.class);
+        mDomainSelectionResolver = Mockito.mock(DomainSelectionResolver.class);
+        mNullCipherNotifier = Mockito.mock(NullCipherNotifier.class);
 
         TelephonyManager.disableServiceHandleCaching();
         PropertyInvalidatedCache.disableForTestMode();
@@ -572,7 +637,7 @@
         doReturn(mTelephonyComponentFactory).when(mTelephonyComponentFactory).inject(anyString());
         doReturn(mSST).when(mTelephonyComponentFactory)
                 .makeServiceStateTracker(nullable(GsmCdmaPhone.class),
-                        nullable(CommandsInterface.class));
+                        nullable(CommandsInterface.class), nullable(FeatureFlags.class));
         doReturn(mEmergencyNumberTracker).when(mTelephonyComponentFactory)
                 .makeEmergencyNumberTracker(nullable(Phone.class),
                         nullable(CommandsInterface.class));
@@ -581,17 +646,17 @@
         doReturn(mUiccProfile).when(mTelephonyComponentFactory)
                 .makeUiccProfile(nullable(Context.class), nullable(CommandsInterface.class),
                         nullable(IccCardStatus.class), anyInt(), nullable(UiccCard.class),
-                        nullable(Object.class));
+                        nullable(Object.class), any(FeatureFlags.class));
         doReturn(mCT).when(mTelephonyComponentFactory)
-                .makeGsmCdmaCallTracker(nullable(GsmCdmaPhone.class));
+                .makeGsmCdmaCallTracker(nullable(GsmCdmaPhone.class), any(FeatureFlags.class));
         doReturn(mIccPhoneBookIntManager).when(mTelephonyComponentFactory)
                 .makeIccPhoneBookInterfaceManager(nullable(Phone.class));
         doReturn(mDisplayInfoController).when(mTelephonyComponentFactory)
-                .makeDisplayInfoController(nullable(Phone.class));
+                .makeDisplayInfoController(nullable(Phone.class), any(FeatureFlags.class));
         doReturn(mWspTypeDecoder).when(mTelephonyComponentFactory)
                 .makeWspTypeDecoder(nullable(byte[].class));
         doReturn(mImsCT).when(mTelephonyComponentFactory)
-                .makeImsPhoneCallTracker(nullable(ImsPhone.class));
+                .makeImsPhoneCallTracker(nullable(ImsPhone.class), any(FeatureFlags.class));
         doReturn(mCdmaSSM).when(mTelephonyComponentFactory)
                 .getCdmaSubscriptionSourceManagerInstance(nullable(Context.class),
                         nullable(CommandsInterface.class), nullable(Handler.class),
@@ -607,14 +672,14 @@
         doReturn(mCarrierActionAgent).when(mTelephonyComponentFactory)
                 .makeCarrierActionAgent(nullable(Phone.class));
         doReturn(mDeviceStateMonitor).when(mTelephonyComponentFactory)
-                .makeDeviceStateMonitor(nullable(Phone.class));
+                .makeDeviceStateMonitor(nullable(Phone.class), any(FeatureFlags.class));
         doReturn(mAccessNetworksManager).when(mTelephonyComponentFactory)
                 .makeAccessNetworksManager(nullable(Phone.class), any(Looper.class));
         doReturn(mNitzStateMachine).when(mTelephonyComponentFactory)
                 .makeNitzStateMachine(nullable(GsmCdmaPhone.class));
         doReturn(mLocaleTracker).when(mTelephonyComponentFactory)
                 .makeLocaleTracker(nullable(Phone.class), nullable(NitzStateMachine.class),
-                        nullable(Looper.class));
+                        nullable(Looper.class), any(FeatureFlags.class));
         doReturn(mEriManager).when(mTelephonyComponentFactory)
                 .makeEriManager(nullable(Phone.class), anyInt());
         doReturn(mLinkBandwidthEstimator).when(mTelephonyComponentFactory)
@@ -622,7 +687,16 @@
         doReturn(mDataProfileManager).when(mTelephonyComponentFactory)
                 .makeDataProfileManager(any(Phone.class), any(DataNetworkController.class),
                         any(DataServiceManager.class), any(Looper.class),
+                        any(FeatureFlags.class),
                         any(DataProfileManager.DataProfileManagerCallback.class));
+        doReturn(mSafetySource).when(mTelephonyComponentFactory)
+                .makeCellularNetworkSecuritySafetySource(any(Context.class));
+        doReturn(mIdentifierDisclosureNotifier)
+                .when(mTelephonyComponentFactory)
+                .makeIdentifierDisclosureNotifier(any(CellularNetworkSecuritySafetySource.class));
+        doReturn(mNullCipherNotifier)
+                .when(mTelephonyComponentFactory)
+                .makeNullCipherNotifier();
 
         //mPhone
         doReturn(mContext).when(mPhone).getContext();
@@ -653,6 +727,7 @@
         doReturn(mVoiceCallSessionStats).when(mPhone).getVoiceCallSessionStats();
         doReturn(mVoiceCallSessionStats).when(mImsPhone).getVoiceCallSessionStats();
         doReturn(mSmsStats).when(mPhone).getSmsStats();
+        doReturn(mTelephonyAnalytics).when(mPhone).getTelephonyAnalytics();
         doReturn(mImsStats).when(mImsPhone).getImsStats();
         mIccSmsInterfaceManager.mDispatchersController = mSmsDispatchersController;
         doReturn(mLinkBandwidthEstimator).when(mPhone).getLinkBandwidthEstimator();
@@ -702,7 +777,7 @@
         doReturn(mSimRecords).when(mUiccProfile).getIccRecords();
         doAnswer(new Answer<IccRecords>() {
             public IccRecords answer(InvocationOnMock invocation) {
-                return (mPhone.isPhoneTypeGsm()) ? mSimRecords : mRuimRecords;
+                return mSimRecords;
             }
         }).when(mUiccProfile).getIccRecords();
 
@@ -763,6 +838,7 @@
                 anyInt(), anyInt());
         doReturn(RIL.RADIO_HAL_VERSION_2_0).when(mPhone).getHalVersion(anyInt());
         doReturn(2).when(mSignalStrength).getLevel();
+        doReturn(mMockRadioConfigProxy).when(mMockRadioConfig).getRadioConfigProxy(any());
 
         // WiFi
         doReturn(mWifiInfo).when(mWifiManager).getConnectionInfo();
@@ -835,6 +911,7 @@
         // Metrics
         doReturn(null).when(mContext).getFileStreamPath(anyString());
         doReturn(mPersistAtomsStorage).when(mMetricsCollector).getAtomsStorage();
+        doReturn(mDefaultNetworkMonitor).when(mMetricsCollector).getDefaultNetworkMonitor();
         doReturn(mWifiManager).when(mContext).getSystemService(eq(Context.WIFI_SERVICE));
         doReturn(mDeviceStateHelper).when(mMetricsCollector).getDeviceStateHelper();
         doReturn(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN)
@@ -842,6 +919,9 @@
                 .getFoldState();
         doReturn(null).when(mContext).getSystemService(eq(Context.DEVICE_STATE_SERVICE));
 
+        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
+
         //Use reflection to mock singletons
         replaceInstance(CallManager.class, "INSTANCE", null, mCallManager);
         replaceInstance(TelephonyComponentFactory.class, "sInstance", null,
@@ -912,7 +992,9 @@
         }
         if (mContext != null) {
             SharedPreferences sharedPreferences = mContext.getSharedPreferences((String) null, 0);
-            sharedPreferences.edit().clear().commit();
+            if (sharedPreferences != null) {
+                sharedPreferences.edit().clear().commit();
+            }
         }
         restoreInstances();
         TelephonyManager.enableServiceHandleCaching();
@@ -936,13 +1018,13 @@
         mContextFixture = null;
         mContext = null;
         mFakeBlockedNumberContentProvider = null;
-        mLock = null;
         mServiceManagerMockedServices.clear();
         mServiceManagerMockedServices = null;
         mPhone = null;
         mTestableLoopers.clear();
         mTestableLoopers = null;
         mTestableLooper = null;
+        DomainSelectionResolver.setDomainSelectionResolver(null);
     }
 
     protected static void logd(String s) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsParserTest.java b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsParserTest.java
index 13fa2d6..77ca1ca 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsParserTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsParserTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.internal.telephony;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.VisualVoicemailSmsParser.WrappedMessageData;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/VoLteServiceStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/VoLteServiceStateTest.java
index 9d2b8a0..7b5297f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/VoLteServiceStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/VoLteServiceStateTest.java
@@ -19,11 +19,11 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.telephony.VoLteServiceState;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import junit.framework.TestCase;
 
-
 public class VoLteServiceStateTest extends TestCase {
 
     @SmallTest
diff --git a/tests/telephonytests/src/com/android/internal/telephony/WapPushCacheTest.java b/tests/telephonytests/src/com/android/internal/telephony/WapPushCacheTest.java
new file mode 100644
index 0000000..f572c08
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/WapPushCacheTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 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;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.Clock;
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
+
+public class WapPushCacheTest extends TelephonyTest {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        WapPushCache.clear();
+        WapPushCache.sTelephonyFacade = new TelephonyFacade();
+        super.tearDown();
+    }
+
+    @Test
+    public void testGetWapMessageSize() {
+        long expectedSize = 100L;
+        byte[] location = "content://mms".getBytes();
+        byte[] transactionId = "123".getBytes();
+
+        WapPushCache.putWapMessageSize(location, transactionId, expectedSize);
+        long size = WapPushCache.getWapMessageSize(location);
+
+        assertEquals(expectedSize, size);
+    }
+
+    @Test
+    public void testGetWapMessageSize_withTransactionIdAppended() {
+        long expectedSize = 100L;
+        byte[] location = "content://mms".getBytes();
+        byte[] transactionId = "123".getBytes();
+        byte[] joinedKey = new byte[location.length + transactionId.length];
+        System.arraycopy(location, 0, joinedKey, 0, location.length);
+        System.arraycopy(transactionId, 0, joinedKey, location.length, transactionId.length);
+
+        WapPushCache.putWapMessageSize(location, transactionId, expectedSize);
+        long size = WapPushCache.getWapMessageSize(joinedKey);
+
+        assertEquals(expectedSize, size);
+    }
+
+    @Test
+    public void testGetWapMessageSize_nonexistentThrows() {
+        assertThrows(NoSuchElementException.class, () ->
+                WapPushCache.getWapMessageSize("content://mms".getBytes())
+        );
+    }
+    @Test
+    public void testGetWapMessageSize_emptyLocationUrlThrows() {
+        assertThrows(IllegalArgumentException.class, () ->
+                WapPushCache.getWapMessageSize(new byte[0])
+        );
+    }
+
+    @Test
+    public void testPutWapMessageSize_invalidValuePreventsInsert() {
+        long expectedSize = 0L;
+        byte[] location = "content://mms".getBytes();
+        byte[] transactionId = "123".getBytes();
+
+        WapPushCache.putWapMessageSize(location, transactionId, expectedSize);
+
+        assertEquals(0, WapPushCache.size());
+    }
+
+    @Test
+    public void testPutWapMessageSize_sizeLimitExceeded_oldestEntryRemoved() {
+        long expectedSize = 100L;
+        for (int i = 0; i < 251; i++) {
+            byte[] location = ("" + i).getBytes();
+            byte[] transactionId = "abc".getBytes();
+            WapPushCache.putWapMessageSize(location, transactionId, expectedSize);
+        }
+
+        // assert one of the entries inserted above has been removed
+        assertEquals(500, WapPushCache.size());
+        // assert last entry added exists
+        assertEquals(expectedSize, WapPushCache.getWapMessageSize("250".getBytes()));
+        // assert the first entry added was removed
+        assertThrows(NoSuchElementException.class, () ->
+                WapPushCache.getWapMessageSize("0".getBytes())
+        );
+    }
+
+    @Test
+    public void testPutWapMessageSize_expiryExceeded_entryRemoved() {
+        long currentTime = Clock.systemUTC().millis();
+        TelephonyFacade facade = mock(TelephonyFacade.class);
+        when(facade.getElapsedSinceBootMillis()).thenReturn(currentTime);
+        WapPushCache.sTelephonyFacade = facade;
+
+        long expectedSize = 100L;
+        byte[] transactionId = "abc".getBytes();
+        byte[] location1 = "old".getBytes();
+        byte[] location2 = "new".getBytes();
+
+        WapPushCache.putWapMessageSize(location1, transactionId, expectedSize);
+        assertEquals(2, WapPushCache.size());
+
+        // advance time
+        when(facade.getElapsedSinceBootMillis())
+                .thenReturn(currentTime + TimeUnit.DAYS.toMillis(14) + 1);
+
+        WapPushCache.putWapMessageSize(location2, transactionId, expectedSize);
+
+        assertEquals(2, WapPushCache.size());
+        assertEquals(expectedSize, WapPushCache.getWapMessageSize(location2));
+        assertThrows(NoSuchElementException.class, () ->
+                WapPushCache.getWapMessageSize(location1)
+        );
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
index 8e40271..c3db35c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
@@ -34,7 +34,8 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Telephony;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -62,6 +63,7 @@
 
     @After
     public void tearDown() throws Exception {
+        WapPushCache.clear();
         mWapPushOverSmsUT = null;
         super.tearDown();
     }
@@ -150,4 +152,29 @@
                 any(UserHandle.class),
                 anyInt());
     }
+
+    @Test @SmallTest
+    public void testDispatchWapPdu_notificationIndInsertedToCache() throws Exception {
+        assertEquals(0, WapPushCache.size());
+        when(mISmsStub.getCarrierConfigValuesForSubscriber(anyInt())).thenReturn(new Bundle());
+
+        doReturn(true).when(mWspTypeDecoder).decodeUintvarInteger(anyInt());
+        doReturn(true).when(mWspTypeDecoder).decodeContentType(anyInt());
+        doReturn((long) 2).when(mWspTypeDecoder).getValue32();
+        doReturn(2).when(mWspTypeDecoder).getDecodedDataLength();
+        doReturn(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO).when(mWspTypeDecoder).getValueString();
+
+        byte[] pdu = {1, 6, 0, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47,
+                118, 110, 100, 46, 119, 97, 112, 46, 109, 109, 115, 45, 109, 101, 115, 115,
+                97, 103, 101, 0, -116, -126, -104, 77, 109, 115, 84, 114, 97, 110, 115, 97,
+                99, 116, 105, 111, 110, 73, 68, 0, -115, 18, -119, 8, -128, 49, 54, 49, 55,
+                56, 50, 54, 57, 49, 54, 56, 47, 84, 89, 80, 69, 61, 80, 76, 77, 78, 0, -118,
+                -128, -114, 2, 3, -24, -120, 3, -127, 3, 3, -12, -128, -106, 84, 101, 115,
+                116, 32, 77, 109, 115, 32, 83, 117, 98, 106, 101, 99, 116, 0, -125, 104, 116,
+                116, 112, 58, 47, 47, 119, 119, 119, 46, 103, 111, 111, 103, 108, 101, 46, 99,
+                111, 109, 47, 115, 97, 100, 102, 100, 100, 0};
+
+        mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler, null, 0, 0L);
+        assertEquals(2, WapPushCache.size());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/analytics/CallAnalyticsProviderTest.java b/tests/telephonytests/src/com/android/internal/telephony/analytics/CallAnalyticsProviderTest.java
new file mode 100644
index 0000000..076ee06
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/analytics/CallAnalyticsProviderTest.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import static android.os.Build.VERSION.INCREMENTAL;
+
+import static com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.CallAnalyticsTable;
+import static com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.DATE_FORMAT;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+public class CallAnalyticsProviderTest {
+
+    @Mock TelephonyAnalyticsUtil mTelephonyAnalyticsUtil;
+    @Mock Cursor mCursor;
+    private CallAnalyticsProvider mCallAnalyticsProvider;
+    private ContentValues mContentValues;
+
+    enum CallStatus {
+        SUCCESS("Success"),
+        FAILURE("Failure");
+        public String value;
+
+        CallStatus(String value) {
+            this.value = value;
+        }
+    }
+
+    enum CallType {
+        NORMAL("Normal Call"),
+        SOS("SOS Call");
+        public String value;
+
+        CallType(String value) {
+            this.value = value;
+        }
+    }
+
+    final String[] mCallInsertionProjection = {
+        TelephonyAnalyticsDatabase.CallAnalyticsTable._ID,
+        TelephonyAnalyticsDatabase.CallAnalyticsTable.COUNT
+    };
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        final String createCallAnalyticsTable =
+                "CREATE TABLE IF NOT EXISTS "
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.TABLE_NAME
+                        + "("
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable._ID
+                        + " INTEGER PRIMARY KEY,"
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.LOG_DATE
+                        + " DATE ,"
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.CALL_STATUS
+                        + " TEXT DEFAULT '',"
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.CALL_TYPE
+                        + " TEXT DEFAULT '',"
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.RAT
+                        + " TEXT DEFAULT '',"
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.SLOT_ID
+                        + " INTEGER ,"
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.FAILURE_REASON
+                        + " TEXT DEFAULT '',"
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.RELEASE_VERSION
+                        + " TEXT DEFAULT '' , "
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.COUNT
+                        + " INTEGER DEFAULT 1 "
+                        + ");";
+        mCallAnalyticsProvider = new CallAnalyticsProvider(mTelephonyAnalyticsUtil, 0);
+        verify(mTelephonyAnalyticsUtil).createTable(createCallAnalyticsTable);
+    }
+
+    @Test
+    public void testAggregate() {
+        String[] columns = {"sum(" + CallAnalyticsTable.COUNT + ")"};
+
+        when(mTelephonyAnalyticsUtil.getCursor(
+                        eq(CallAnalyticsTable.TABLE_NAME),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull()))
+                .thenReturn(mCursor);
+        when(mTelephonyAnalyticsUtil.getCursor(
+                        anyString(),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        anyString(),
+                        isNull(),
+                        anyString(),
+                        anyString()))
+                .thenReturn(mCursor);
+
+        when(mTelephonyAnalyticsUtil.getCountFromCursor(eq(mCursor)))
+                .thenReturn(
+                        100L /*totalCalls*/,
+                        50L /*totalFailedCalls*/,
+                        40L /*normalCalls*/,
+                        10L /*failedNormalCall*/,
+                        60L /*sosCalls*/,
+                        40L /*failedSosCall*/);
+        ArrayList<String> actual = mCallAnalyticsProvider.aggregate();
+        verify(mTelephonyAnalyticsUtil, times(6))
+                .getCursor(
+                        eq(CallAnalyticsTable.TABLE_NAME),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull());
+        assertEquals("\tTotal Normal Calls = " + 40 /*normalCalls*/, actual.get(1));
+        assertEquals("\tPercentage Failure of Normal Calls = 25.00%", actual.get(2));
+    }
+
+    @Test
+    public void testGetMaxFailureVersion() {
+        String[] columns = {CallAnalyticsTable.RELEASE_VERSION};
+        String selection =
+                CallAnalyticsTable.CALL_STATUS + " = ? AND " + CallAnalyticsTable.SLOT_ID + " = ? ";
+        String[] selectionArgs = {"Failure", Integer.toString(0 /* slotIndex */)};
+        String groupBy = CallAnalyticsTable.RELEASE_VERSION;
+        String orderBy = "SUM(" + CallAnalyticsTable.COUNT + ") DESC ";
+        String limit = "1";
+        when(mTelephonyAnalyticsUtil.getCursor(
+                        anyString(),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        anyString(),
+                        isNull(),
+                        anyString(),
+                        anyString()))
+                .thenReturn(mCursor);
+        when(mTelephonyAnalyticsUtil.getCountFromCursor(any(Cursor.class)))
+                .thenReturn(10L /* count */);
+        when(mTelephonyAnalyticsUtil.getCountFromCursor(isNull())).thenReturn(10L /* count */);
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex(CallAnalyticsTable.RELEASE_VERSION))
+                .thenReturn(0 /* releaseVersionColumnIndex */);
+        when(mCursor.getString(0)).thenReturn("1.1.1.1" /* version */);
+        ArrayList<String> actual = mCallAnalyticsProvider.aggregate();
+        verify(mTelephonyAnalyticsUtil)
+                .getCursor(
+                        eq(CallAnalyticsTable.TABLE_NAME),
+                        eq(columns),
+                        eq(selection),
+                        eq(selectionArgs),
+                        eq(groupBy),
+                        isNull(),
+                        eq(orderBy),
+                        eq(limit));
+        assertEquals(
+                actual.get(actual.size() - 2 /* array index for max failure at version info */),
+                "\tMax Call(Normal+SOS) Failures at Version : 1.1.1.1");
+    }
+
+    private ContentValues getContentValues(
+            String callType, String callStatus, int slotId, String rat, String failureReason) {
+        ContentValues values = new ContentValues();
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        values.put(CallAnalyticsTable.LOG_DATE, dateToday);
+        values.put(CallAnalyticsTable.CALL_TYPE, callType);
+        values.put(CallAnalyticsTable.CALL_STATUS, callStatus);
+        values.put(CallAnalyticsTable.SLOT_ID, slotId);
+        values.put(CallAnalyticsTable.RAT, rat);
+        values.put(CallAnalyticsTable.FAILURE_REASON, failureReason);
+        values.put(CallAnalyticsTable.RELEASE_VERSION, INCREMENTAL);
+        return values;
+    }
+
+    private void whenConditionForGetCursor() {
+        when(mTelephonyAnalyticsUtil.getCursor(
+                        anyString(),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull()))
+                .thenReturn(mCursor);
+    }
+
+    private void verifyForGetCursor(
+            String[] callInsertionProjection,
+            String callSuccessInsertionSelection,
+            String[] selectionArgs) {
+
+        verify(mTelephonyAnalyticsUtil)
+                .getCursor(
+                        eq(TelephonyAnalyticsDatabase.CallAnalyticsTable.TABLE_NAME),
+                        eq(callInsertionProjection),
+                        eq(callSuccessInsertionSelection),
+                        eq(selectionArgs),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull());
+    }
+
+    @Test
+    public void testSuccessCall() {
+        int slotId = 0;
+        String callType = "Normal Call";
+        String callStatus = "Success";
+        String rat = "LTE";
+        String failureReason = "User Disconnects";
+        int count = 5;
+
+        final String callSuccessInsertionSelection =
+                TelephonyAnalyticsDatabase.CallAnalyticsTable.CALL_TYPE
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.LOG_DATE
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.CALL_STATUS
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.CallAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        ContentValues values = getContentValues(callType, callStatus, slotId, rat, failureReason);
+
+        String[] selectionArgs =
+                new String[] {
+                    values.getAsString(TelephonyAnalyticsDatabase.CallAnalyticsTable.CALL_TYPE),
+                    values.getAsString(TelephonyAnalyticsDatabase.CallAnalyticsTable.LOG_DATE),
+                    callStatus,
+                    values.getAsString(TelephonyAnalyticsDatabase.CallAnalyticsTable.SLOT_ID)
+                };
+        whenConditionForGetCursor();
+        mCallAnalyticsProvider.insertDataToDb(callType, callStatus, slotId, rat, failureReason);
+        verifyForGetCursor(mCallInsertionProjection, callSuccessInsertionSelection, selectionArgs);
+    }
+
+    @Test
+    public void testFailureCall() {
+        int slotId = 0;
+        String callType = "Normal Call";
+        String callStatus = "Failure";
+        String rat = "LTE";
+        String failureReason = "Network Detach";
+        int count = 5;
+        final String callFailedInsertionSelection =
+                CallAnalyticsTable.LOG_DATE
+                        + " = ? AND "
+                        + CallAnalyticsTable.CALL_STATUS
+                        + " = ? AND "
+                        + CallAnalyticsTable.CALL_TYPE
+                        + " = ? AND "
+                        + CallAnalyticsTable.SLOT_ID
+                        + " = ? AND "
+                        + CallAnalyticsTable.RAT
+                        + " = ? AND "
+                        + CallAnalyticsTable.FAILURE_REASON
+                        + " = ? AND "
+                        + CallAnalyticsTable.RELEASE_VERSION
+                        + " = ? ";
+        ContentValues values = getContentValues(callType, callStatus, slotId, rat, failureReason);
+        String[] selectionArgs = {
+            values.getAsString(CallAnalyticsTable.LOG_DATE),
+            values.getAsString(CallAnalyticsTable.CALL_STATUS),
+            values.getAsString(CallAnalyticsTable.CALL_TYPE),
+            values.getAsString(CallAnalyticsTable.SLOT_ID),
+            values.getAsString(CallAnalyticsTable.RAT),
+            values.getAsString(CallAnalyticsTable.FAILURE_REASON),
+            values.getAsString(CallAnalyticsTable.RELEASE_VERSION)
+        };
+        whenConditionForGetCursor();
+        mCallAnalyticsProvider.insertDataToDb(callType, callStatus, slotId, rat, failureReason);
+        verifyForGetCursor(mCallInsertionProjection, callFailedInsertionSelection, selectionArgs);
+    }
+
+    public void setUpTestForUpdateEntryIfExistsOrInsert() throws NoSuchMethodException {
+        Method updateEntryIfExistsOrInsert =
+                CallAnalyticsProvider.class.getDeclaredMethod(
+                        "updateEntryIfExistsOrInsert", Cursor.class, ContentValues.class);
+        updateEntryIfExistsOrInsert.setAccessible(true);
+        mContentValues = new ContentValues();
+        mContentValues.put(CallAnalyticsTable.CALL_STATUS, "Success");
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenCursorNull()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert =
+                CallAnalyticsProvider.class.getDeclaredMethod(
+                        "updateEntryIfExistsOrInsert", Cursor.class, ContentValues.class);
+        updateEntryIfExistsOrInsert.setAccessible(true);
+        ContentValues values = new ContentValues();
+        values.put(CallAnalyticsTable.CALL_STATUS, "Success");
+        updateEntryIfExistsOrInsert.invoke(mCallAnalyticsProvider, null, values);
+        verify(mTelephonyAnalyticsUtil).insert(eq(CallAnalyticsTable.TABLE_NAME), eq(values));
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenCursorInvalid()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert =
+                CallAnalyticsProvider.class.getDeclaredMethod(
+                        "updateEntryIfExistsOrInsert", Cursor.class, ContentValues.class);
+        updateEntryIfExistsOrInsert.setAccessible(true);
+        ContentValues values = new ContentValues();
+        values.put(CallAnalyticsTable.CALL_STATUS, "Success");
+        when(mCursor.moveToFirst()).thenReturn(false);
+        updateEntryIfExistsOrInsert.invoke(mCallAnalyticsProvider, mCursor, values);
+        verify(mTelephonyAnalyticsUtil).insert(eq(CallAnalyticsTable.TABLE_NAME), eq(values));
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenCursorValidUpdateSuccess()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert =
+                CallAnalyticsProvider.class.getDeclaredMethod(
+                        "updateEntryIfExistsOrInsert", Cursor.class, ContentValues.class);
+        updateEntryIfExistsOrInsert.setAccessible(true);
+
+        ContentValues values = new ContentValues();
+        values.put(CallAnalyticsTable.CALL_STATUS, "Success");
+
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex(CallAnalyticsTable._ID)).thenReturn(0 /* idColumnIndex */);
+        when(mCursor.getColumnIndex(CallAnalyticsTable.COUNT)).thenReturn(1 /* countColumnIndex */);
+        when(mCursor.getInt(0 /* idColumnIndex */)).thenReturn(100 /* id */);
+        when(mCursor.getInt(1 /* countColumnIndex */)).thenReturn(5 /* count*/);
+
+        String updateSelection = CallAnalyticsTable._ID + " = ? ";
+        String[] updateSelectionArgs = {String.valueOf(100 /* id */)};
+
+        updateEntryIfExistsOrInsert.invoke(mCallAnalyticsProvider, mCursor, values);
+
+        values.put(CallAnalyticsTable.COUNT, 6 /* newCount */);
+        verify(mTelephonyAnalyticsUtil)
+                .update(
+                        eq(CallAnalyticsTable.TABLE_NAME),
+                        eq(values),
+                        eq(updateSelection),
+                        eq(updateSelectionArgs));
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenUpdateFailedDueToInvalidIdIndex()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert =
+                CallAnalyticsProvider.class.getDeclaredMethod(
+                        "updateEntryIfExistsOrInsert", Cursor.class, ContentValues.class);
+        updateEntryIfExistsOrInsert.setAccessible(true);
+
+        ContentValues values = new ContentValues();
+        values.put(CallAnalyticsTable.CALL_STATUS, "Success");
+
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex(CallAnalyticsTable._ID)).thenReturn(-1 /* idColumnIndex */);
+        when(mCursor.getColumnIndex(CallAnalyticsTable.COUNT)).thenReturn(1 /* countColumnIndex */);
+        updateEntryIfExistsOrInsert.invoke(mCallAnalyticsProvider, mCursor, values);
+        verifyNoMoreInteractions(mTelephonyAnalyticsUtil);
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenUpdateFailedDueToInvalidCountIndex()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert =
+                CallAnalyticsProvider.class.getDeclaredMethod(
+                        "updateEntryIfExistsOrInsert", Cursor.class, ContentValues.class);
+        updateEntryIfExistsOrInsert.setAccessible(true);
+
+        ContentValues values = new ContentValues();
+        values.put(CallAnalyticsTable.CALL_STATUS, "Success");
+
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex(CallAnalyticsTable._ID)).thenReturn(0 /* idColumnIndex */);
+        when(mCursor.getColumnIndex(CallAnalyticsTable.COUNT))
+                .thenReturn(-1 /* countColumnIndex */);
+        updateEntryIfExistsOrInsert.invoke(mCallAnalyticsProvider, mCursor, values);
+        verifyNoMoreInteractions(mTelephonyAnalyticsUtil);
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenUpdateFailedDueToInvalidColumnIndex()
+            throws NoSuchMethodException {
+        Method updateEntryIfExistsOrInsert =
+                CallAnalyticsProvider.class.getDeclaredMethod(
+                        "updateEntryIfExistsOrInsert", Cursor.class, ContentValues.class);
+        updateEntryIfExistsOrInsert.setAccessible(true);
+
+        ContentValues values = new ContentValues();
+        values.put(CallAnalyticsTable.CALL_STATUS, "Success");
+
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex(CallAnalyticsTable._ID)).thenReturn(-1 /* idColumnIndex */);
+        when(mCursor.getColumnIndex(CallAnalyticsTable.COUNT))
+                .thenReturn(-1 /* countColumnIndex */);
+        verifyNoMoreInteractions(mTelephonyAnalyticsUtil);
+    }
+
+    @After
+    public void tearDown() {
+        mCallAnalyticsProvider = null;
+        mContentValues = null;
+        mTelephonyAnalyticsUtil = null;
+        mCursor = null;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/analytics/ServiceStateAnalyticsProviderTest.java b/tests/telephonytests/src/com/android/internal/telephony/analytics/ServiceStateAnalyticsProviderTest.java
new file mode 100644
index 0000000..dcc1c73
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/analytics/ServiceStateAnalyticsProviderTest.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import static android.os.Build.VERSION.INCREMENTAL;
+
+import static com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.DATE_FORMAT;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+
+import com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.ServiceStateAnalyticsTable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+
+public class ServiceStateAnalyticsProviderTest {
+    @Mock TelephonyAnalyticsUtil mTelephonyAnalyticsUtil;
+    @Mock Cursor mCursor;
+    @Mock TelephonyAnalytics.ServiceStateAnalytics.TimeStampedServiceState mState;
+    private ContentValues mContentValues;
+
+    final int mSlotIndex = 0;
+    ServiceStateAnalyticsProvider mServiceStateAnalyticsProvider;
+    final String mCreateServiceStateTableQuery =
+            "CREATE TABLE IF NOT EXISTS "
+                    + ServiceStateAnalyticsTable.TABLE_NAME
+                    + " ( "
+                    + ServiceStateAnalyticsTable._ID
+                    + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+                    + ServiceStateAnalyticsTable.LOG_DATE
+                    + " DATE ,"
+                    + ServiceStateAnalyticsTable.SLOT_ID
+                    + " INTEGER , "
+                    + ServiceStateAnalyticsTable.TIME_DURATION
+                    + " INTEGER ,"
+                    + ServiceStateAnalyticsTable.RAT
+                    + " TEXT ,"
+                    + ServiceStateAnalyticsTable.DEVICE_STATUS
+                    + " TEXT ,"
+                    + ServiceStateAnalyticsTable.RELEASE_VERSION
+                    + " TEXT "
+                    + ");";
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        assert (mTelephonyAnalyticsUtil != null);
+        mServiceStateAnalyticsProvider =
+                new ServiceStateAnalyticsProvider(mTelephonyAnalyticsUtil, mSlotIndex);
+        verify(mTelephonyAnalyticsUtil).createTable(mCreateServiceStateTableQuery);
+        mContentValues = getDummyContentValue();
+    }
+
+    @Test
+    public void valid() {
+        assert (mServiceStateAnalyticsProvider != null);
+        assert (mTelephonyAnalyticsUtil != null);
+        assert (mCursor != null);
+        assert (mState != null);
+    }
+
+    @Test
+    public void testInsertDataToDb() {
+        TelephonyAnalytics.ServiceStateAnalytics.TimeStampedServiceState lastState =
+                new TelephonyAnalytics.ServiceStateAnalytics.TimeStampedServiceState(
+                        0 /*slotIndex*/,
+                        "LTE" /*rat*/,
+                        "IN_SERVICE" /*deviceStatus*/,
+                        233423424 /*timestampStart*/);
+        ContentValues values = new ContentValues();
+        long timeInterval = 343443434 /*endTimeStamp*/ - lastState.getTimestampStart();
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        values.put(ServiceStateAnalyticsTable.LOG_DATE, dateToday);
+        values.put(ServiceStateAnalyticsTable.TIME_DURATION, timeInterval);
+        values.put(ServiceStateAnalyticsTable.SLOT_ID, lastState.getSlotIndex());
+        values.put(ServiceStateAnalyticsTable.RAT, lastState.getRAT());
+        values.put(ServiceStateAnalyticsTable.DEVICE_STATUS, lastState.getDeviceStatus());
+        values.put(ServiceStateAnalyticsTable.RELEASE_VERSION, INCREMENTAL);
+
+        final String[] serviceStateInsertionColumns = {
+            ServiceStateAnalyticsTable._ID, ServiceStateAnalyticsTable.TIME_DURATION
+        };
+        final String serviceStateInsertionSelection =
+                ServiceStateAnalyticsTable.LOG_DATE
+                        + " = ? AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? AND "
+                        + ServiceStateAnalyticsTable.RAT
+                        + " = ? AND "
+                        + ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " = ? AND "
+                        + ServiceStateAnalyticsTable.RELEASE_VERSION
+                        + " = ? ";
+        final String[] selectionArgs = {
+            values.getAsString(ServiceStateAnalyticsTable.LOG_DATE),
+            values.getAsString(ServiceStateAnalyticsTable.SLOT_ID),
+            values.getAsString(ServiceStateAnalyticsTable.RAT),
+            values.getAsString(ServiceStateAnalyticsTable.DEVICE_STATUS),
+            values.getAsString(ServiceStateAnalyticsTable.RELEASE_VERSION)
+        };
+        when(mTelephonyAnalyticsUtil.getCursor(
+                        anyString(),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull()))
+                .thenReturn(mCursor);
+        mServiceStateAnalyticsProvider.insertDataToDb(lastState, 343443434 /*endTimeStamp*/);
+
+        verify(mTelephonyAnalyticsUtil)
+                .getCursor(
+                        eq(ServiceStateAnalyticsTable.TABLE_NAME),
+                        eq(serviceStateInsertionColumns),
+                        eq(serviceStateInsertionSelection),
+                        eq(selectionArgs),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull());
+    }
+
+    private Method setReflectionForTestingUpdateEntryIfExistsOrInsert()
+            throws NoSuchMethodException {
+        Method updateEntryIfExistsOrInsert =
+                ServiceStateAnalyticsProvider.class.getDeclaredMethod(
+                        "updateIfEntryExistsOtherwiseInsert", Cursor.class, ContentValues.class);
+        updateEntryIfExistsOrInsert.setAccessible(true);
+        return updateEntryIfExistsOrInsert;
+    }
+
+    private ContentValues getDummyContentValue() {
+        ContentValues values = new ContentValues();
+        values.put(ServiceStateAnalyticsTable.DEVICE_STATUS, "IN_SERVICE" /*DeviceStatus*/);
+        values.put(ServiceStateAnalyticsTable.TIME_DURATION, 423234 /*Time Duration*/);
+        return values;
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenCursorNull()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert = setReflectionForTestingUpdateEntryIfExistsOrInsert();
+        updateEntryIfExistsOrInsert.invoke(mServiceStateAnalyticsProvider, null, mContentValues);
+        verify(mTelephonyAnalyticsUtil)
+                .insert(eq(ServiceStateAnalyticsTable.TABLE_NAME), eq(mContentValues));
+    }
+
+    @Test
+    public void testUpdateIfEntryExistsOtherwiseInsertWhenEntryNotExist()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert = setReflectionForTestingUpdateEntryIfExistsOrInsert();
+        when(mCursor.moveToFirst()).thenReturn(false);
+        updateEntryIfExistsOrInsert.invoke(mServiceStateAnalyticsProvider, null, mContentValues);
+        verify(mTelephonyAnalyticsUtil)
+                .insert(eq(ServiceStateAnalyticsTable.TABLE_NAME), eq(mContentValues));
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenCursorValidAndUpdateSuccess()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert = setReflectionForTestingUpdateEntryIfExistsOrInsert();
+
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable._ID)).thenReturn(0 /* idIndex */);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.TIME_DURATION))
+                .thenReturn(1 /* timeDurationIndex */);
+        when(mCursor.getInt(0 /* idIndex */)).thenReturn(100 /* ID */);
+        when(mCursor.getInt(1 /* timeDurationIndex */)).thenReturn(523535 /* oldTimeDuration */);
+
+        String updateSelection = ServiceStateAnalyticsTable._ID + " = ? ";
+        String[] updateSelectionArgs = {Integer.toString(100 /* ID */)};
+
+        updateEntryIfExistsOrInsert.invoke(mServiceStateAnalyticsProvider, mCursor, mContentValues);
+        mContentValues.put(
+                ServiceStateAnalyticsTable.TIME_DURATION, 946769 /* updatedTimeDuration */);
+        verify(mTelephonyAnalyticsUtil)
+                .update(
+                        eq(ServiceStateAnalyticsTable.TABLE_NAME),
+                        eq(mContentValues),
+                        eq(updateSelection),
+                        eq(updateSelectionArgs));
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenUpdateFailedDueToInvalidIdIndex()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert = setReflectionForTestingUpdateEntryIfExistsOrInsert();
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable._ID)).thenReturn(-1 /* idIndex */);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.TIME_DURATION))
+                .thenReturn(1 /* timeDurationIndex */);
+        updateEntryIfExistsOrInsert.invoke(mServiceStateAnalyticsProvider, mCursor, mContentValues);
+        verifyNoMoreInteractions(mTelephonyAnalyticsUtil);
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenUpdateFailedDueToInvalidDurationIndex()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert = setReflectionForTestingUpdateEntryIfExistsOrInsert();
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable._ID)).thenReturn(0 /* idIndex */);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.TIME_DURATION))
+                .thenReturn(-1 /* timeDurationIndex */);
+        updateEntryIfExistsOrInsert.invoke(mServiceStateAnalyticsProvider, mCursor, mContentValues);
+        verifyNoMoreInteractions(mTelephonyAnalyticsUtil);
+    }
+
+    @Test
+    public void testUpdateEntryIfExistsOrInsertWhenUpdateFailedDueToAllInvalidIndex()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method updateEntryIfExistsOrInsert = setReflectionForTestingUpdateEntryIfExistsOrInsert();
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable._ID)).thenReturn(-1 /* idIndex */);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.TIME_DURATION))
+                .thenReturn(-1 /* timeDurationIndex */);
+        updateEntryIfExistsOrInsert.invoke(mServiceStateAnalyticsProvider, mCursor, mContentValues);
+        verifyNoMoreInteractions(mTelephonyAnalyticsUtil);
+    }
+
+    private void setWhenClauseForGetCursor(
+            String[] columns, String selection, String[] selectionArgs) {
+        when(mTelephonyAnalyticsUtil.getCursor(
+                        eq(ServiceStateAnalyticsTable.TABLE_NAME),
+                        eq(columns),
+                        eq(selection),
+                        eq(selectionArgs),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull()))
+                .thenReturn(mCursor);
+    }
+
+    private void verifyClause(String[] columns, String selection, String[] selectionArgs) {
+        verify(mTelephonyAnalyticsUtil)
+                .getCursor(
+                        eq(ServiceStateAnalyticsTable.TABLE_NAME),
+                        eq(columns),
+                        eq(selection),
+                        eq(selectionArgs),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull());
+    }
+
+    private void setUpNullCursorReturn() {
+        when(mTelephonyAnalyticsUtil.getCursor(
+                        anyString(),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull()))
+                .thenReturn(null);
+    }
+
+    @Test
+    public void testTotalUpTimeThroughAggregate() {
+        HashMap<String, Long> empty = new HashMap<>();
+        String[] columns = {"SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ")"};
+        String selection = ServiceStateAnalyticsTable.SLOT_ID + " = ? ";
+        String[] selectionArgs = {Integer.toString(mSlotIndex)};
+        setWhenClauseForGetCursor(columns, selection, selectionArgs);
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getLong(0 /* columnIndex */)).thenReturn(1000000L /* upTime */);
+
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyClause(columns, selection, selectionArgs);
+        assertEquals(
+                actual.get(0 /* Array Index for Total Uptime*/),
+                "Total UpTime = " + 1000000 /* upTime */ + " millis");
+    }
+
+    @Test
+    public void testTotalUpTimeWhenCursorNullThroughAggregate() {
+        String[] columns = {"SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ")"};
+        String selection = ServiceStateAnalyticsTable.SLOT_ID + " = ? ";
+        String[] selectionArgs = {Integer.toString(mSlotIndex)};
+        setUpNullCursorReturn();
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyClause(columns, selection, selectionArgs);
+        assertEquals(
+                actual.get(0 /* Array Index for Total Uptime*/),
+                "Total UpTime = " + 0 /*upTime */ + " millis");
+    }
+
+    @Test
+    public void testTotalUpTimeWhenCursorInvalidThroughAggregate() {
+        String[] columns = {"SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ")"};
+        String selection = ServiceStateAnalyticsTable.SLOT_ID + " = ? ";
+        String[] selectionArgs = {Integer.toString(mSlotIndex)};
+        setWhenClauseForGetCursor(columns, selection, selectionArgs);
+        when(mCursor.moveToFirst()).thenReturn(false);
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyClause(columns, selection, selectionArgs);
+        assertEquals(
+                actual.get(0 /* Array Index for Total Uptime*/),
+                "Total UpTime = " + 0 /*upTime */ + " millis");
+    }
+
+    @Test
+    public void testOutOfServiceDurationThroughAggregate() {
+        setUpTotalTime();
+        String[] columns = {"SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ")"};
+        String selection =
+                ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " != ? "
+                        + " AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"IN_SERVICE", Integer.toString(mSlotIndex)};
+
+        when(mCursor.moveToFirst()).thenReturn(true, true);
+        when(mCursor.getLong(0 /*columnIndex*/))
+                .thenReturn(1000000L /*totalUpTime*/, 100000L /*outOfServiceTime*/);
+        setWhenClauseForGetCursor(columns, selection, selectionArgs);
+
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyClause(columns, selection, selectionArgs);
+        assertEquals(
+                actual.get(1),
+                "Out of Service Time = "
+                        + 100000 /*outOfServiceTime*/
+                        + " millis, Percentage "
+                        + "10.00"
+                        + "%");
+    }
+
+    @Test
+    public void testOutOfServiceDurationWhenCursorNullThroughAggregate() {
+        HashMap<String, Long> empty = new HashMap<>();
+        String[] columns = {"SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ")"};
+        String selection =
+                ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " != ? "
+                        + " AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"IN_SERVICE", Integer.toString(mSlotIndex)};
+        setUpNullCursorReturn();
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyClause(columns, selection, selectionArgs);
+        boolean check =
+                actual.get(1 /* ArrayIndex for Out Of Service Time Info */)
+                        .contains("Out of Service Time = 0");
+        assertTrue(check);
+    }
+
+    @Test
+    public void testOutOfServiceDurationWhenCursorInvalidThroughAggregate() {
+        HashMap<String, Long> empty = new HashMap<>();
+        String[] columns = {"SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ")"};
+        String selection =
+                ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " != ? "
+                        + " AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"IN_SERVICE", Integer.toString(mSlotIndex)};
+        setWhenClauseForGetCursor(columns, selection, selectionArgs);
+        when(mCursor.moveToFirst()).thenReturn(false);
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyClause(columns, selection, selectionArgs);
+        boolean check = actual.get(1).contains("Out of Service Time = 0");
+        assertTrue(check);
+    }
+
+    private void whenClauseForGroupByPresent(
+            String[] columns, String selection, String[] selectionArgs, String groupBy) {
+        when(mTelephonyAnalyticsUtil.getCursor(
+                        eq(ServiceStateAnalyticsTable.TABLE_NAME),
+                        eq(columns),
+                        eq(selection),
+                        eq(selectionArgs),
+                        eq(groupBy),
+                        isNull(),
+                        isNull(),
+                        isNull()))
+                .thenReturn(mCursor);
+    }
+
+    private void verifyGroupBy(
+            String[] columns, String selection, String[] selectionArgs, String groupBy) {
+        verify(mTelephonyAnalyticsUtil)
+                .getCursor(
+                        eq(ServiceStateAnalyticsTable.TABLE_NAME),
+                        eq(columns),
+                        eq(selection),
+                        eq(selectionArgs),
+                        eq(groupBy),
+                        isNull(),
+                        isNull(),
+                        isNull());
+    }
+
+    private void setUpTotalTime() {
+        String[] columns = {"SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ")"};
+        String selection = ServiceStateAnalyticsTable.SLOT_ID + " = ? ";
+        String[] selectionArgs = {Integer.toString(mSlotIndex)};
+        setWhenClauseForGetCursor(columns, selection, selectionArgs);
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getLong(0 /*columnIndex*/)).thenReturn(1000000L /*upTime*/);
+    }
+
+    @Test
+    public void testOutOfServiceDurationByReasonWhenValid() {
+        setUpTotalTime();
+        String[] columns = {
+            ServiceStateAnalyticsTable.DEVICE_STATUS,
+            "SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ") AS totalTime"
+        };
+        String selection =
+                ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " != ? AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"IN_SERVICE", Integer.toString(mSlotIndex)};
+        String groupBy = ServiceStateAnalyticsTable.DEVICE_STATUS;
+        whenClauseForGroupByPresent(columns, selection, selectionArgs, groupBy);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.DEVICE_STATUS))
+                .thenReturn(0 /*deviceStatusIndex*/);
+        when(mCursor.getColumnIndex("totalTime")).thenReturn(1 /*totalTimeIndex*/);
+        when(mCursor.moveToNext()).thenReturn(true, false);
+        when(mCursor.getString(0 /*deviceStatusIndex*/)).thenReturn("NO_NETWORK" /*oosReason*/);
+        when(mCursor.getLong(1 /*totalTimeIndex*/)).thenReturn(100000L /*oosTime*/);
+
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyGroupBy(columns, selection, selectionArgs, groupBy);
+        assertEquals(
+                actual.get(2 /*oosReasonDumpArrayIndex*/),
+                "Out of service Reason = " + "NO_NETWORK" + ", Percentage = " + "10.00" + "%");
+    }
+
+    @Test
+    public void testOutOfServiceDurationByReasonWhenNoDataInCursor() {
+        String[] columns = {
+            ServiceStateAnalyticsTable.DEVICE_STATUS,
+            "SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ") AS totalTime"
+        };
+        String selection =
+                ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " != ? AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"IN_SERVICE", Integer.toString(mSlotIndex)};
+        String groupBy = ServiceStateAnalyticsTable.DEVICE_STATUS;
+        HashMap<String, Long> outOfServiceDurationByReason = new HashMap<>();
+        outOfServiceDurationByReason.put("NO_NETWORK", 10000L);
+
+        whenClauseForGroupByPresent(columns, selection, selectionArgs, groupBy);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.DEVICE_STATUS))
+                .thenReturn(0 /*deviceStatusIndex*/);
+        when(mCursor.getColumnIndex("totalTime")).thenReturn(1 /*totalTimeIndex*/);
+        when(mCursor.moveToNext()).thenReturn(false);
+
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyGroupBy(columns, selection, selectionArgs, groupBy);
+        assertEquals(actual.size(), 2 /*expectedArraySize*/);
+    }
+
+    @Test
+    public void testOutOfServiceDurationByReasonWhenReasonIndexInvalid() {
+        String[] columns = {
+            ServiceStateAnalyticsTable.DEVICE_STATUS,
+            "SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ") AS totalTime"
+        };
+        String selection =
+                ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " != ? AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"IN_SERVICE", Integer.toString(mSlotIndex)};
+        String groupBy = ServiceStateAnalyticsTable.DEVICE_STATUS;
+        HashMap<String, Long> outOfServiceDurationByReason = new HashMap<>();
+        outOfServiceDurationByReason.put("NO_NETWORK", 10000L);
+
+        whenClauseForGroupByPresent(columns, selection, selectionArgs, groupBy);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.DEVICE_STATUS))
+                .thenReturn(-1 /*deviceStatusIndex*/);
+        when(mCursor.getColumnIndex("totalTime")).thenReturn(1 /*totalTimeIndex*/);
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyGroupBy(columns, selection, selectionArgs, groupBy);
+        assertEquals(actual.size(), 2 /*expectedArraySize*/);
+    }
+
+    @Test
+    public void testOutOfServiceDurationByReasonWhenTimeIndexInvalid() {
+        String[] columns = {
+            ServiceStateAnalyticsTable.DEVICE_STATUS,
+            "SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ") AS totalTime"
+        };
+        String selection =
+                ServiceStateAnalyticsTable.DEVICE_STATUS
+                        + " != ? AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"IN_SERVICE", Integer.toString(mSlotIndex)};
+        String groupBy = ServiceStateAnalyticsTable.DEVICE_STATUS;
+        HashMap<String, Long> outOfServiceDurationByReason = new HashMap<>();
+        outOfServiceDurationByReason.put("NO_NETWORK", 10000L);
+
+        whenClauseForGroupByPresent(columns, selection, selectionArgs, groupBy);
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.DEVICE_STATUS))
+                .thenReturn(0 /*deviceStatusIndex*/);
+        when(mCursor.getColumnIndex("totalTime")).thenReturn(-1 /*totalTimeIndex*/);
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        verifyGroupBy(columns, selection, selectionArgs, groupBy);
+        assertEquals(actual.size(), 2 /*expectedArraySize*/);
+    }
+
+    private void setUpForTestingGetInServiceDurationByRat() {
+        String[] columns = {
+            ServiceStateAnalyticsTable.RAT,
+            "SUM(" + ServiceStateAnalyticsTable.TIME_DURATION + ") AS totalTime"
+        };
+        String selection =
+                ServiceStateAnalyticsTable.RAT
+                        + " != ? AND "
+                        + ServiceStateAnalyticsTable.SLOT_ID
+                        + " = ? ";
+        String[] selectionArgs = {"NA", Integer.toString(mSlotIndex)};
+        String groupBy = ServiceStateAnalyticsTable.RAT;
+        whenClauseForGroupByPresent(columns, selection, selectionArgs, groupBy);
+    }
+
+    @Test
+    public void testInServiceDurationByRatWhenDataPresent() {
+        setUpForTestingGetInServiceDurationByRat();
+        setUpTotalTime();
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.RAT)).thenReturn(0 /* ratIndex */);
+        when(mCursor.getColumnIndex("totalTime")).thenReturn(1 /* totalTimeIndex */);
+        when(mCursor.moveToNext()).thenReturn(true, false);
+        when(mCursor.getString(0 /* ratIndex */)).thenReturn("LTE" /* inServiceRat */);
+        when(mCursor.getLong(1 /* totalTimeIndex */)).thenReturn(200000L /* inServiceTime */);
+
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        assertEquals(
+                actual.get(2 /*arrayIndex For In Service RAT Information */),
+                "IN_SERVICE RAT : " + "LTE" + ", Percentage = 20.00" + "%");
+    }
+
+    @Test
+    public void testInServiceDurationByRatWhenDataNotPresent() {
+        setUpForTestingGetInServiceDurationByRat();
+        setUpTotalTime();
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.RAT)).thenReturn(0 /* ratIndex */);
+        when(mCursor.getColumnIndex("totalTime")).thenReturn(1 /* totalTimeIndex */);
+        when(mCursor.moveToNext()).thenReturn(false);
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        assertEquals(actual.size(), 2 /* expectedArraySize */);
+    }
+
+    @Test
+    public void testInServiceDurationByRatWhenInvalidRatIndex() {
+        setUpForTestingGetInServiceDurationByRat();
+        setUpTotalTime();
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.RAT)).thenReturn(-1 /* ratIndex */);
+        when(mCursor.getColumnIndex("totalTime")).thenReturn(1 /* totalTimeIndex */);
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        assertEquals(actual.size(), 2 /* expectedArraySize */);
+    }
+
+    @Test
+    public void testInServiceDurationByRatWhenInvalidTimeIndex() {
+        setUpForTestingGetInServiceDurationByRat();
+        setUpTotalTime();
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.RAT)).thenReturn(0 /* ratIndex */);
+        when(mCursor.getColumnIndex("totalTime")).thenReturn(-1 /* totalTimeIndex */);
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        assertEquals(actual.size(), 2 /* expectedArraySize */);
+    }
+
+    @Test
+    public void testInServiceDurationByRatWhenBothIndexInvalid() {
+        setUpForTestingGetInServiceDurationByRat();
+        setUpTotalTime();
+        when(mCursor.getColumnIndex(ServiceStateAnalyticsTable.RAT)).thenReturn(-1 /* ratIndex */);
+        when(mCursor.getColumnIndex("totalTime")).thenReturn(-1 /* totalTimeIndex */);
+        ArrayList<String> actual = mServiceStateAnalyticsProvider.aggregate();
+        assertEquals(actual.size(), 2 /* expectedArraySize */);
+    }
+
+    @Test
+    public void testDeleteOldAndOverflowDataWhenLastDeletedDateEqualsToday() {
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        mServiceStateAnalyticsProvider.setDateOfDeletedRecordsServiceStateTable(dateToday);
+        TelephonyAnalytics.ServiceStateAnalytics.TimeStampedServiceState
+                mockTimeStampedServiceState =
+                        mock(
+                                TelephonyAnalytics.ServiceStateAnalytics.TimeStampedServiceState
+                                        .class);
+        mServiceStateAnalyticsProvider.insertDataToDb(
+                mockTimeStampedServiceState, 100L /* endTimeStamp */);
+    }
+
+    @After
+    public void tearDown() {
+        mServiceStateAnalyticsProvider = null;
+        mCursor = null;
+        mTelephonyAnalyticsUtil = null;
+        mContentValues = null;
+        mState = null;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/analytics/SmsMmsAnalyticsProviderTest.java b/tests/telephonytests/src/com/android/internal/telephony/analytics/SmsMmsAnalyticsProviderTest.java
new file mode 100644
index 0000000..54acfeb
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/analytics/SmsMmsAnalyticsProviderTest.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2023 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.analytics;
+
+import static android.os.Build.VERSION.INCREMENTAL;
+
+import static com.android.internal.telephony.analytics.TelephonyAnalyticsDatabase.DATE_FORMAT;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.concurrent.CountDownLatch;
+
+public class SmsMmsAnalyticsProviderTest {
+    @Mock TelephonyAnalyticsUtil mTelephonyAnalyticsUtil;
+
+    SmsMmsAnalyticsProvider mSmsMmsAnalyticsProvider;
+    private TelephonyAnalyticsUtil mMockTelephonyAnalyticsUtil;
+    private static final String[] SMS_MMS_INSERTION_PROJECTION = {
+        TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable._ID,
+        TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.COUNT
+    };
+
+    @Mock Cursor mCursor;
+    final String mCreateTableQuery =
+            "CREATE TABLE IF NOT EXISTS SmsMmsDataLogs("
+                    + "_id INTEGER PRIMARY KEY,"
+                    + "LogDate DATE ,"
+                    + "SmsMmsStatus TEXT DEFAULT '',"
+                    + "SmsMmsType TEXT DEFAULT '',"
+                    + "SlotID INTEGER , "
+                    + "RAT TEXT DEFAULT '',"
+                    + "FailureReason TEXT DEFAULT '',"
+                    + "ReleaseVersion TEXT DEFAULT '' , "
+                    + "Count INTEGER DEFAULT 1 );";
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mSmsMmsAnalyticsProvider = new SmsMmsAnalyticsProvider(mTelephonyAnalyticsUtil, 0);
+        mMockTelephonyAnalyticsUtil = mock(TelephonyAnalyticsUtil.class);
+        verify(mTelephonyAnalyticsUtil).createTable(mCreateTableQuery);
+    }
+
+    @Test
+    public void testCursor() {
+        assert (mCursor != null);
+        assert (mTelephonyAnalyticsUtil != null);
+    }
+
+    private ContentValues getContentValues(
+            String status, String type, String rat, String failureReason) {
+        ContentValues values = new ContentValues();
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.LOG_DATE, dateToday);
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_STATUS, status);
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_TYPE, type);
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RAT, rat);
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SLOT_ID, 0);
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.FAILURE_REASON, failureReason);
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RELEASE_VERSION, INCREMENTAL);
+
+        return values;
+    }
+
+    private void mockAndVerifyCall(String selection, String[] selectionArgs) {
+        when(mTelephonyAnalyticsUtil.getCursor(
+                        eq(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.TABLE_NAME),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull()))
+                .thenReturn(mCursor);
+
+        verify(mTelephonyAnalyticsUtil)
+                .getCursor(
+                        eq(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.TABLE_NAME),
+                        eq(SMS_MMS_INSERTION_PROJECTION),
+                        eq(selection),
+                        eq(selectionArgs),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull());
+    }
+
+    @Test
+    public void testFailureSms() {
+        String status = "Failure";
+        String type = "SMS Outgoing";
+        String rat = "LTE";
+        String failureReason = "SIM_ABSENT";
+        final String smsMmsInsertionFailureSelection =
+                TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.LOG_DATE
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_STATUS
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_TYPE
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RAT
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SLOT_ID
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.FAILURE_REASON
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RELEASE_VERSION
+                        + " = ? ";
+        ContentValues values = getContentValues(status, type, rat, failureReason);
+
+        String[] selectionArgs =
+                new String[] {
+                    values.getAsString(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.LOG_DATE),
+                    values.getAsString(
+                            TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_STATUS),
+                    values.getAsString(
+                            TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_TYPE),
+                    values.getAsString(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RAT),
+                    values.getAsString(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SLOT_ID),
+                    values.getAsString(
+                            TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.FAILURE_REASON),
+                    values.getAsString(
+                            TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RELEASE_VERSION)
+                };
+        mSmsMmsAnalyticsProvider.insertDataToDb(status, type, rat, failureReason);
+        mockAndVerifyCall(smsMmsInsertionFailureSelection, selectionArgs);
+    }
+
+    @Test
+    public void testSuccessSms() {
+        String status = "Success";
+        String type = "SMS Outgoing";
+        String rat = "LTE";
+        String failureReason = "SIM_ABSENT";
+
+        final String smsMmsInsertionSuccessSelection =
+                TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.LOG_DATE
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_TYPE
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_STATUS
+                        + " = ? AND "
+                        + TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SLOT_ID
+                        + " = ? ";
+
+        ContentValues values = getContentValues(status, type, rat, failureReason);
+        String[] selectionArgs =
+                new String[] {
+                    values.getAsString(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.LOG_DATE),
+                    values.getAsString(
+                            TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_TYPE),
+                    values.getAsString(
+                            TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SMS_MMS_STATUS),
+                    values.getAsString(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.SLOT_ID)
+                };
+
+        mSmsMmsAnalyticsProvider.insertDataToDb(status, type, rat, failureReason);
+
+        mockAndVerifyCall(smsMmsInsertionSuccessSelection, selectionArgs);
+    }
+
+    @Test
+    public void testUpdateIfEntryExistsOtherwiseInsertWhenEntryNotExist() {
+        ContentValues values = new ContentValues();
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.COUNT, 5);
+        when(mCursor.moveToFirst()).thenReturn(false);
+        mSmsMmsAnalyticsProvider.updateIfEntryExistsOtherwiseInsert(mCursor, values);
+        verify(mTelephonyAnalyticsUtil)
+                .insert(eq(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.TABLE_NAME), eq(values));
+    }
+
+    @Test
+    public void testUpdateIfEntryExistsOtherwiseInsertWhenCursorNull() {
+        ContentValues values = new ContentValues();
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.COUNT, 5);
+        mSmsMmsAnalyticsProvider.updateIfEntryExistsOtherwiseInsert(null, values);
+        verify(mTelephonyAnalyticsUtil)
+                .insert(eq(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.TABLE_NAME), eq(values));
+    }
+
+    @Test
+    public void testUpdateIfEntryExistsOtherwiseInsertWhenEntryExists() {
+        ContentValues values = new ContentValues();
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RAT, "LTE");
+
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex("_id")).thenReturn(0);
+        when(mCursor.getColumnIndex("Count")).thenReturn(1);
+        when(mCursor.getInt(0)).thenReturn(100);
+        when(mCursor.getInt(1)).thenReturn(2);
+
+        mSmsMmsAnalyticsProvider.updateIfEntryExistsOtherwiseInsert(mCursor, values);
+
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.COUNT, 3);
+
+        String updateSelection = TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable._ID + " = ? ";
+        String[] updateSelectionArgs = {String.valueOf(100)};
+
+        verify(mTelephonyAnalyticsUtil)
+                .update(
+                        eq(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.TABLE_NAME),
+                        eq(values),
+                        eq(updateSelection),
+                        eq(updateSelectionArgs));
+    }
+
+    @Test
+    public void testUpdateIfEntryExistsOtherwiseInsertWithInvalidIdColumnIndex() {
+        ContentValues values = new ContentValues();
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RAT, "LTE");
+
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex("_id")).thenReturn(-1);
+        when(mCursor.getColumnIndex("Count")).thenReturn(1);
+        when(mCursor.getInt(0)).thenReturn(100);
+        when(mCursor.getInt(1)).thenReturn(2);
+        mSmsMmsAnalyticsProvider.updateIfEntryExistsOtherwiseInsert(mCursor, values);
+        verifyNoMoreInteractions(mTelephonyAnalyticsUtil);
+    }
+
+    @Test
+    public void testUpdateIfEntryExistsOtherwiseInsertWithInvalidCountColumnIndex() {
+        ContentValues values = new ContentValues();
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RAT, "LTE");
+
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex("_id")).thenReturn(0);
+        when(mCursor.getColumnIndex("Count")).thenReturn(-1);
+        when(mCursor.getInt(0)).thenReturn(100);
+        when(mCursor.getInt(1)).thenReturn(2);
+        mSmsMmsAnalyticsProvider.updateIfEntryExistsOtherwiseInsert(mCursor, values);
+        verifyNoMoreInteractions(mTelephonyAnalyticsUtil);
+    }
+
+    @Test
+    public void testUpdateIfEntryExistsOtherwiseInsertWithInvalidColumnIndex() {
+        ContentValues values = new ContentValues();
+        values.put(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.RAT, "LTE");
+        when(mCursor.moveToFirst()).thenReturn(true);
+        when(mCursor.getColumnIndex("_id")).thenReturn(-1);
+        when(mCursor.getColumnIndex("Count")).thenReturn(-1);
+        when(mCursor.getInt(0)).thenReturn(100);
+        when(mCursor.getInt(1)).thenReturn(2);
+        mSmsMmsAnalyticsProvider.updateIfEntryExistsOtherwiseInsert(mCursor, values);
+        verifyNoMoreInteractions(mTelephonyAnalyticsUtil);
+    }
+
+    @Test
+    public void testDeleteWhenDateEqualsToday() {
+        String status = "Success";
+        String type = "SMS Outgoing";
+        String rat = "LTE";
+        String failureReason = "SIM_ABSENT";
+
+        String dateToday = DATE_FORMAT.format(Calendar.getInstance().toInstant());
+        mSmsMmsAnalyticsProvider.setDateOfDeletedRecordsSmsMmsTable(dateToday);
+        mSmsMmsAnalyticsProvider.insertDataToDb(status, type, rat, failureReason);
+        verify(mTelephonyAnalyticsUtil, times(0))
+                .delete(anyString(), anyString(), any(String[].class));
+    }
+
+    @Test
+    public void testDeleteWhenDateNotNullAndNotEqualsToday() {
+        String status = "Success";
+        String type = "SMS Outgoing";
+        String rat = "LTE";
+        String failureReason = "SIM_ABSENT";
+        String dateToday = "1965-10-12";
+        mSmsMmsAnalyticsProvider.setDateOfDeletedRecordsSmsMmsTable(dateToday);
+        mSmsMmsAnalyticsProvider.insertDataToDb(status, type, rat, failureReason);
+        verify(mTelephonyAnalyticsUtil, times(1))
+                .deleteOverflowAndOldData(anyString(), anyString(), anyString());
+    }
+
+    @Test
+    public void testAggregate() throws NoSuchFieldException, IllegalAccessException {
+        final CountDownLatch latch = new CountDownLatch(9);
+        Cursor mockCursor = mock(Cursor.class);
+        when(mMockTelephonyAnalyticsUtil.getCursor(
+                        eq(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.TABLE_NAME),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull()))
+                .thenReturn(mockCursor);
+        when(mMockTelephonyAnalyticsUtil.getCursor(
+                        eq(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.TABLE_NAME),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        anyString(),
+                        isNull(),
+                        isNull(),
+                        isNull()))
+                .thenReturn(mockCursor);
+        when(mMockTelephonyAnalyticsUtil.getCountFromCursor(eq(mockCursor)))
+                .thenReturn(
+                        80L /*totalOutgoingSms*/,
+                        70L /*totalIncomingSms*/,
+                        60L /*totalOutgoingMms*/,
+                        50L /*totalIncomingMms*/,
+                        40L /*totalFailedOutgoingSms*/,
+                        30L /*totalFailedIncomingSms*/,
+                        20L /*totalFailedOutgoingMms*/,
+                        10L /*totalFailedIncomingMms*/);
+
+        when(mockCursor.getColumnIndex("RAT")).thenReturn(0 /*ratIndex*/);
+        when(mockCursor.getColumnIndex("count")).thenReturn(1 /*countIndex*/);
+        when(mockCursor.moveToNext()).thenReturn(true, false);
+        when(mockCursor.getString(0 /*ratIndex*/)).thenReturn("LTE" /* RAT*/);
+        when(mockCursor.getInt(1 /*countIndex*/)).thenReturn(10 /* Count */);
+
+        SmsMmsAnalyticsProvider smsMmsAnalyticsProvider =
+                new SmsMmsAnalyticsProvider(mMockTelephonyAnalyticsUtil, 0 /* slotIndex */);
+        ArrayList<String> received = smsMmsAnalyticsProvider.aggregate();
+
+        verify(mMockTelephonyAnalyticsUtil, times(8))
+                .getCursor(
+                        eq(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.TABLE_NAME),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        isNull(),
+                        isNull(),
+                        isNull(),
+                        isNull());
+        verify(mMockTelephonyAnalyticsUtil, times(1))
+                .getCursor(
+                        eq(TelephonyAnalyticsDatabase.SmsMmsAnalyticsTable.TABLE_NAME),
+                        any(String[].class),
+                        anyString(),
+                        any(String[].class),
+                        anyString(),
+                        isNull(),
+                        isNull(),
+                        isNull());
+        verify(mMockTelephonyAnalyticsUtil, times(8)).getCountFromCursor(eq(mockCursor));
+        verify(mockCursor).getColumnIndex("RAT");
+        verify(mockCursor).getColumnIndex("count");
+
+        assertEquals(
+                "Total Outgoing Sms Count = 80",
+                received.get(0 /* array index for totalOutgoingSms */));
+        assertEquals(
+                "Total Incoming Sms Count = 70",
+                received.get(1 /* array index for totalIncomingSms */));
+        assertEquals(
+                "Failed Outgoing SMS Count = 40 Percentage failure rate for Outgoing SMS :50.00%",
+                received.get(2 /* array index for totalFailedOutgoingSms */));
+        assertEquals(
+                "Failed Incoming SMS Count = 30 Percentage failure rate for Incoming SMS :42.86%",
+                received.get(3 /* array index for totalFailedIncomingSms */));
+        assertEquals(
+                "Overall Fail Percentage = 46.67%",
+                received.get(4 /* array index for overall fail percentage */));
+        assertEquals(
+                "Failed SMS Count for RAT : LTE = 10, Percentage = 6.67%",
+                received.get(5 /* array index for  failedSmsTypeCountByRat */));
+    }
+
+    @After
+    public void tearDown() {
+        mCursor = null;
+        mTelephonyAnalyticsUtil = null;
+        mMockTelephonyAnalyticsUtil = null;
+        mSmsMmsAnalyticsProvider = null;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java
index f2c1870..a07ddbe 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java
@@ -144,7 +144,7 @@
         mSimulatedCommands = mock(SimulatedCommands.class);
         mSimulatedCommands.setIccIoResultForApduLogicalChannel(mIccIoResult);
         mUiccProfile = new UiccProfile(mContext, mSimulatedCommands, mIccCardStatus,
-                0 /* phoneId */, mUiccCard, new Object());
+                0 /* phoneId */, mUiccCard, new Object(), mFeatureFlags);
         processAllMessages();
         logd("Created UiccProfile");
         processAllMessages();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
index 3445939..24f7d2c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
@@ -42,11 +42,11 @@
 import android.os.UserManager;
 import android.provider.Telephony;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
 
 import com.android.internal.telephony.FakeSmsContentProvider;
 import com.android.internal.telephony.InboundSmsHandler;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
index c9a16f5..100c391 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
@@ -21,9 +21,9 @@
 import android.telephony.SmsCbMessage;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.RILUtils;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java
index 0315776..1f52cea 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java
@@ -16,16 +16,17 @@
 
 package com.android.internal.telephony.cdma;
 
+import static org.mockito.Mockito.*;
+
 import android.os.HandlerThread;
 import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsDispatchersController;
 import com.android.internal.telephony.TelephonyTest;
 
-import static org.mockito.Mockito.*;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
index 0e0f113..d2a6922 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
@@ -18,7 +18,8 @@
 
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.SmsHeader;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/d2d/CommunicatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/d2d/CommunicatorTest.java
index 4681dbc..9008e16 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/d2d/CommunicatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/d2d/CommunicatorTest.java
@@ -25,9 +25,9 @@
 import static org.mockito.Mockito.verify;
 
 import android.telecom.Connection;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/d2d/DtmfTransportConversionTest.java b/tests/telephonytests/src/com/android/internal/telephony/d2d/DtmfTransportConversionTest.java
index 5bc382f..c2c2a61 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/d2d/DtmfTransportConversionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/d2d/DtmfTransportConversionTest.java
@@ -21,9 +21,10 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.mockito.Mockito.mock;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.TestExecutorService;
 
 import org.junit.After;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/d2d/DtmfTransportTest.java b/tests/telephonytests/src/com/android/internal/telephony/d2d/DtmfTransportTest.java
index 68ddd23..e1525d0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/d2d/DtmfTransportTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/d2d/DtmfTransportTest.java
@@ -26,9 +26,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.telephony.TestExecutorService;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportConversionTest.java b/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportConversionTest.java
index 545507b..3cdab56 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportConversionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportConversionTest.java
@@ -27,9 +27,10 @@
 
 import android.os.Handler;
 import android.telephony.ims.RtpHeaderExtension;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportTest.java b/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportTest.java
index 55559eb..fe78c8d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportTest.java
@@ -25,12 +25,11 @@
 import android.os.Handler;
 import android.telephony.ims.RtpHeaderExtension;
 import android.telephony.ims.RtpHeaderExtensionType;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
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 4d116a8..91ed03f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -41,11 +42,13 @@
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.NetworkService;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.DataServiceCallback;
 import android.telephony.data.IQualifiedNetworksService;
 import android.telephony.data.IQualifiedNetworksServiceCallback;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
 
@@ -55,6 +58,11 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class AccessNetworksManagerTest extends TelephonyTest {
@@ -68,8 +76,37 @@
 
     // The real callback passed created by AccessNetworksManager.
     private IQualifiedNetworksServiceCallback.Stub mQnsCallback;
-
     private PersistableBundle mBundle;
+    private List<Integer> mIIntegerConsumerResults =  new ArrayList<>();
+    private Semaphore mIIntegerConsumerSemaphore = new Semaphore(0);
+    private IIntegerConsumer mIIntegerConsumer = new IIntegerConsumer.Stub() {
+        @Override
+        public void accept(int result) {
+            logd("mIIntegerConsumer: result=" + result);
+            mIIntegerConsumerResults.add(result);
+            try {
+                mIIntegerConsumerSemaphore.release();
+            } catch (Exception ex) {
+                logd("mIIntegerConsumer: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    private boolean waitForIIntegerConsumerResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mIIntegerConsumerSemaphore.tryAcquire(500 /* Timeout */,
+                        TimeUnit.MILLISECONDS)) {
+                    logd("Timeout to receive IIntegerConsumer() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                logd("waitForIIntegerConsumerResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
 
     private void addQnsService() throws Exception {
         ServiceInfo QnsInfo = new ServiceInfo();
@@ -116,7 +153,8 @@
         }).when(mMockedCallback).invokeFromExecutor(any(Runnable.class));
 
         mMockedDataConfigManager = Mockito.mock(DataConfigManager.class);
-        mAccessNetworksManager = new AccessNetworksManager(mPhone, Looper.myLooper());
+        mAccessNetworksManager =
+                new AccessNetworksManager(mPhone, Looper.myLooper(), mFeatureFlags);
 
         processAllMessages();
         replaceInstance(AccessNetworksManager.class, "mDataConfigManager",
@@ -281,6 +319,33 @@
         assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
                 NetworkCapabilities.NET_CAPABILITY_XCAP)).isEqualTo(
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-
     }
+
+    @Test
+    public void testRequestNetworkValidation_WithFlagEnabled()  throws Exception {
+        when(mFeatureFlags.networkValidation()).thenReturn(true);
+
+        mQnsCallback.onNetworkValidationRequested(NetworkCapabilities.NET_CAPABILITY_IMS,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertThat(waitForIIntegerConsumerResult(1 /*numOfEvents*/)).isFalse();
+    }
+
+    @Test
+    public void testRequestNetworkValidation_WithFlagDisabled() throws Exception {
+        mIIntegerConsumerResults.clear();
+        when(mFeatureFlags.networkValidation()).thenReturn(false);
+
+        mQnsCallback.onNetworkValidationRequested(NetworkCapabilities.NET_CAPABILITY_IMS,
+                mIIntegerConsumer);
+        processAllMessages();
+
+        assertThat(waitForIIntegerConsumerResult(1 /*numOfEvents*/)).isTrue();
+        assertThat((long) mIIntegerConsumerResults.get(0))
+                .isEqualTo(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
+        verify(mDataNetworkController, never()).requestNetworkValidation(
+                NetworkCapabilities.NET_CAPABILITY_IMS,
+                mIntegerConsumer);
+    }
+
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java
index b6d77e9..0c7342e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony.data;
 
+import static android.telephony.data.ApnSetting.INFRASTRUCTURE_CELLULAR;
+import static android.telephony.data.ApnSetting.INFRASTRUCTURE_SATELLITE;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
@@ -25,6 +28,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.PersistableBundle;
+import android.provider.Telephony;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -39,6 +43,7 @@
 import java.lang.reflect.Modifier;
 import java.net.InetAddress;
 import java.util.List;
+import java.util.Set;
 
 public class ApnSettingTest extends TelephonyTest {
 
@@ -154,6 +159,9 @@
         assertTrue(createApnSetting(
                 ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS | ApnSetting.TYPE_EMERGENCY)
                 .canHandleType(ApnSetting.TYPE_EMERGENCY));
+        assertTrue(createApnSetting(
+                ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_RCS | ApnSetting.TYPE_EMERGENCY)
+                .canHandleType(ApnSetting.TYPE_RCS));
         assertFalse(createApnSetting(ApnSetting.TYPE_ALL)
                 .canHandleType(ApnSetting.TYPE_MCX));
         assertTrue(createApnSetting(
@@ -200,12 +208,16 @@
         final String[] dummyStringArr = new String[] {"dummy"};
         final InetAddress dummyProxyAddress = InetAddress.getByAddress(new byte[]{0, 0, 0, 0});
         final Uri dummyUri = Uri.parse("www.google.com");
+
+        final Set<String> excludedFields = Set.of("mEditedStatus");
+
         // base apn
         ApnSetting baseApn = createApnSetting(ApnSetting.TYPE_MMS | ApnSetting.TYPE_DEFAULT);
         Field[] fields = ApnSetting.class.getDeclaredFields();
         for (Field f : fields) {
             int modifiers = f.getModifiers();
-            if (Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers)) {
+            if (Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers)
+                    || excludedFields.contains(f.getName())) {
                 continue;
             }
             f.setAccessible(true);
@@ -378,4 +390,63 @@
                 .build();
         assertEquals("proxy.mobile.att.net", apn3.getMmsProxyAddressAsString());
     }
+
+    @Test
+    public void testBuild_InfrastructureBitmask() {
+        int infrastructureBitmask = INFRASTRUCTURE_CELLULAR | INFRASTRUCTURE_SATELLITE;
+        ApnSetting apn1 = new ApnSetting.Builder()
+                .setId(1234)
+                .setOperatorNumeric("310260")
+                .setEntryName("mms")
+                .setApnName("mms")
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS | ApnSetting.TYPE_DEFAULT)
+                .setProtocol(ApnSetting.PROTOCOL_IPV4V6)
+                .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE))
+                .build();
+        // InfrastructureBitmask default value set to '3(cellular|satellite)'
+        assertEquals(infrastructureBitmask, apn1.getInfrastructureBitmask());
+
+        infrastructureBitmask = INFRASTRUCTURE_CELLULAR;
+        ApnSetting apn2 = new ApnSetting.Builder()
+                .setId(1235)
+                .setOperatorNumeric("310260")
+                .setEntryName("mms")
+                .setApnName("mms")
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS | ApnSetting.TYPE_DEFAULT)
+                .setProtocol(ApnSetting.PROTOCOL_IPV4V6)
+                .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE))
+                .setInfrastructureBitmask(infrastructureBitmask)
+                .build();
+        // InfrastructureBitmask value set to '1(cellular)'
+        assertEquals(infrastructureBitmask, apn2.getInfrastructureBitmask());
+    }
+
+    @Test
+    public void testEditedStatus() {
+        ApnSetting apn = new ApnSetting.Builder()
+                .setId(1234)
+                .setOperatorNumeric("310260")
+                .setEntryName("mms")
+                .setApnName("mms")
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS | ApnSetting.TYPE_DEFAULT)
+                .setProtocol(ApnSetting.PROTOCOL_IPV4V6)
+                .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE))
+                .setEditedStatus(Telephony.Carriers.USER_EDITED)
+                .build();
+        assertEquals(Telephony.Carriers.USER_EDITED, apn.getEditedStatus());
+
+        ApnSetting apn2 = new ApnSetting.Builder()
+                .setId(1234)
+                .setOperatorNumeric("310260")
+                .setEntryName("mms")
+                .setApnName("mms")
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS | ApnSetting.TYPE_DEFAULT)
+                .setProtocol(ApnSetting.PROTOCOL_IPV4V6)
+                .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE))
+                .setEditedStatus(Telephony.Carriers.CARRIER_EDITED)
+                .build();
+
+        // The edited status should not affect equals
+        assertEquals(apn, apn2);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
index 7ac3a17..50ee5c8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
@@ -20,9 +20,15 @@
 
 import static com.android.internal.telephony.data.AutoDataSwitchController.EVALUATION_REASON_DATA_SETTINGS_CHANGED;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
@@ -31,6 +37,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.app.AlarmManager;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.net.NetworkCapabilities;
@@ -40,8 +47,11 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -55,6 +65,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Map;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class AutoDataSwitchControllerTest extends TelephonyTest {
@@ -62,23 +74,37 @@
     private static final int EVENT_DISPLAY_INFO_CHANGED = 2;
     private static final int EVENT_EVALUATE_AUTO_SWITCH = 3;
     private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4;
-    private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5;
+    private static final int EVENT_STABILITY_CHECK_PASSED = 5;
 
     private static final int PHONE_1 = 0;
     private static final int SUB_1 = 1;
     private static final int PHONE_2 = 1;
     private static final int SUB_2 = 2;
     private static final int MAX_RETRY = 5;
+    private static final int SCORE_TOLERANCE = 100;
+    private static final int GOOD_RAT_SIGNAL_SCORE = 200;
+    private static final int BAD_RAT_SIGNAL_SCORE = 50;
+    private boolean mIsNonTerrestrialNetwork = false;
     // Mocked
     private AutoDataSwitchController.AutoDataSwitchControllerCallback mMockedPhoneSwitcherCallback;
+    private AlarmManager mMockedAlarmManager;
 
+    // Real
+    private TelephonyDisplayInfo mGoodTelephonyDisplayInfo;
+    private TelephonyDisplayInfo mBadTelephonyDisplayInfo;
     private int mDefaultDataSub;
     private AutoDataSwitchController mAutoDataSwitchControllerUT;
+    private Map<Integer, AlarmManager.OnAlarmListener> mEventsToAlarmListener;
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        mGoodTelephonyDisplayInfo = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_NR,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, false /*roaming*/);
+        mBadTelephonyDisplayInfo = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UMTS,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false /*roaming*/);
         mMockedPhoneSwitcherCallback =
                 mock(AutoDataSwitchController.AutoDataSwitchControllerCallback.class);
+        mMockedAlarmManager = mock(AlarmManager.class);
 
         doReturn(PHONE_1).when(mPhone).getPhoneId();
         doReturn(SUB_1).when(mPhone).getSubId();
@@ -91,17 +117,34 @@
 
         mPhones = new Phone[]{mPhone, mPhone2};
         for (Phone phone : mPhones) {
+            ServiceState ss = new ServiceState();
+
+            ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                    .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                    .setRegistrationState(
+                            NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
+                    .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                    .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
+                    .build());
+
+            doReturn(ss).when(phone).getServiceState();
             doReturn(mSST).when(phone).getServiceStateTracker();
             doReturn(mDisplayInfoController).when(phone).getDisplayInfoController();
             doReturn(mSignalStrengthController).when(phone).getSignalStrengthController();
             doReturn(mSignalStrength).when(phone).getSignalStrength();
+            doReturn(mDataNetworkController).when(phone).getDataNetworkController();
+            doReturn(mDataConfigManager).when(mDataNetworkController).getDataConfigManager();
             doAnswer(invocation -> phone.getSubId() == mDefaultDataSub)
                     .when(phone).isUserDataEnabled();
         }
-        doReturn(new int[mPhones.length]).when(mSubscriptionManagerService)
+        doReturn(new int[]{SUB_1, SUB_2}).when(mSubscriptionManagerService)
                 .getActiveSubIdList(true);
         doAnswer(invocation -> {
             int subId = (int) invocation.getArguments()[0];
+            return subId == SUB_1 ? PHONE_1 : PHONE_2;
+        }).when(mSubscriptionManagerService).getPhoneId(anyInt());
+        doAnswer(invocation -> {
+            int subId = (int) invocation.getArguments()[0];
 
             if (!SubscriptionManager.isUsableSubIdValue(subId)) return null;
 
@@ -111,22 +154,45 @@
         }).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
 
-        // Change resource overlay
+        // Change data config
         doReturn(true).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired();
-        doReturn(1L).when(mDataConfigManager)
+        doReturn(10000L).when(mDataConfigManager)
                 .getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        doReturn(120000L).when(mDataConfigManager)
+                .getAutoDataSwitchPerformanceStabilityTimeThreshold();
         doReturn(MAX_RETRY).when(mDataConfigManager).getAutoDataSwitchValidationMaxRetry();
+        doReturn(SCORE_TOLERANCE).when(mDataConfigManager).getAutoDataSwitchScoreTolerance();
+        doAnswer(invocation -> {
+            TelephonyDisplayInfo displayInfo = (TelephonyDisplayInfo) invocation.getArguments()[0];
+            SignalStrength signalStrength = (SignalStrength) invocation.getArguments()[1];
+            if (displayInfo == mGoodTelephonyDisplayInfo
+                    || signalStrength.getLevel() > SignalStrength.SIGNAL_STRENGTH_MODERATE) {
+                return GOOD_RAT_SIGNAL_SCORE;
+            }
+            return BAD_RAT_SIGNAL_SCORE;
+        }).when(mDataConfigManager).getAutoDataSwitchScore(any(TelephonyDisplayInfo.class),
+                any(SignalStrength.class));
 
         setDefaultDataSubId(SUB_1);
         doReturn(PHONE_1).when(mPhoneSwitcher).getPreferredDataPhoneId();
 
         mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(),
-                mPhoneSwitcher, mMockedPhoneSwitcherCallback);
+                mPhoneSwitcher, mFeatureFlags, mMockedPhoneSwitcherCallback);
+
+        replaceInstance(AutoDataSwitchController.class, "mAlarmManager",
+                mAutoDataSwitchControllerUT, mMockedAlarmManager);
+        mEventsToAlarmListener = getPrivateField(mAutoDataSwitchControllerUT,
+                "mEventsToAlarmListener", Map.class);
+
+        doReturn(true).when(mFeatureFlags).autoSwitchAllowRoaming();
+        doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
     }
 
     @After
     public void tearDown() throws Exception {
         mAutoDataSwitchControllerUT = null;
+        mGoodTelephonyDisplayInfo = null;
+        mBadTelephonyDisplayInfo = null;
         super.tearDown();
     }
 
@@ -139,8 +205,22 @@
         // Verify attempting to switch
         verify(mMockedPhoneSwitcherCallback).onRequireValidation(PHONE_2, true/*needValidation*/);
 
-        // 1. Service state becomes not ideal - primary is available again
+        // 1.1 Service state becomes not ideal - secondary lost its advantage score,
+        // but primary is OOS, so continue to switch.
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
+        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_POOR);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback, never())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+
+        // 1.2 Service state becomes not ideal - secondary lost its advantage score,
+        // since primary is in service, no need to switch.
+        clearInvocations(mMockedPhoneSwitcherCallback);
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
+        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_POOR);
         processAllFutureMessages();
 
         verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
@@ -177,13 +257,186 @@
     }
 
     @Test
+    public void testRoaming_prefer_home_over_roam() {
+        // DDS -> nDDS: Prefer Home over Roaming
+        prepareIdealUsesNonDdsCondition();
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(PHONE_2, true/*needValidation*/);
+
+        // nDDS -> DDS: Prefer Home over Roaming
+        doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
+    }
+
+    @Test
+    public void testRoaming_prefer_roam_over_nonTerrestrial() {
+        // DDS -> nDDS: Prefer Roaming over non-terrestrial
+        prepareIdealUsesNonDdsCondition();
+        mIsNonTerrestrialNetwork = true;
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        mIsNonTerrestrialNetwork = false;
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(PHONE_2, true/*needValidation*/);
+
+        // nDDS -> DDS: Prefer Roaming over non-terrestrial
+        doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
+        mIsNonTerrestrialNetwork = false;
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mIsNonTerrestrialNetwork = true;
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
+        mIsNonTerrestrialNetwork = false;
+    }
+
+    @Test
+    public void testRoaming_roaming_but_roam_disabled() {
+        // Disable RAT + signalStrength base switching.
+        doReturn(-1).when(mDataConfigManager).getAutoDataSwitchScoreTolerance();
+        mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(),
+                mPhoneSwitcher, mFeatureFlags, mMockedPhoneSwitcherCallback);
+
+        // On primary phone
+        // 1.1 Both roaming, user allow roaming on both phone, no need to switch.
+        prepareIdealUsesNonDdsCondition();
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        processAllFutureMessages();
+        clearInvocations(mMockedPhoneSwitcherCallback);
+
+        mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback, never()).onRequireValidation(anyInt(),
+                anyBoolean()/*needValidation*/);
+
+        // 1.2 Both roaming, but roaming is only allowed on the backup phone.
+        doReturn(false).when(mPhone).getDataRoamingEnabled();
+        mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(PHONE_2, true/*needValidation*/);
+
+        // On backup phone
+        doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
+        // 2.1 Both roaming, user allow roaming on both phone, prefer default.
+        doReturn(true).when(mPhone).getDataRoamingEnabled();
+        mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
+
+        // 2.1 Both roaming, but roaming is only allowed on the default phone.
+        doReturn(false).when(mPhone2).getDataRoamingEnabled();
+        mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                false/*needValidation*/);
+    }
+
+    @Test
+    public void testRoaming_same_roaming_condition_uses_rat_signalStrength() {
+        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
+        // On primary phone
+        // 1. Both roaming, user allow roaming on both phone, uses RAT score to decide switch.
+        prepareIdealUsesNonDdsCondition();
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(PHONE_2, true/*needValidation*/);
+
+        // On backup phone
+        doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
+        // 2. Both roaming, user allow roaming on both phone, uses RAT score to decide switch.
+        signalStrengthChanged(PHONE_1, SignalStrength.SIGNAL_STRENGTH_GREAT);
+        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_POOR);
+        displayInfoChanged(PHONE_1, mGoodTelephonyDisplayInfo);
+        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
+    }
+
+    @Test
+    public void testCancelSwitch_onPrimary_rat_signalStrength() {
+        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
+        // 4.1.1 Display info and signal strength on secondary phone became bad,
+        // but primary is still OOS, so still switch to the secondary.
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
+        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback, never())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+
+        // 4.1.2 Display info and signal strength on secondary phone became bad,
+        // but primary become service, then don't switch.
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+        clearInvocations(mMockedPhoneSwitcherCallback, mMockedAlarmManager);
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
+        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback, atLeastOnce())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedAlarmManager, atLeastOnce()).cancel(mEventsToAlarmListener.get(
+                EVENT_STABILITY_CHECK_PASSED));
+
+        // 4.2 Display info on default phone became good just as the secondary
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+        clearInvocations(mMockedPhoneSwitcherCallback, mMockedAlarmManager);
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        displayInfoChanged(PHONE_1, mGoodTelephonyDisplayInfo);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback, atLeastOnce())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedAlarmManager, atLeastOnce()).cancel(mEventsToAlarmListener.get(
+                EVENT_STABILITY_CHECK_PASSED));
+
+        // 4.3 Signal strength on default phone became just as good as the secondary
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+        clearInvocations(mMockedPhoneSwitcherCallback, mMockedAlarmManager);
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        signalStrengthChanged(PHONE_1, SignalStrength.SIGNAL_STRENGTH_GREAT);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback, atLeastOnce())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedAlarmManager, atLeastOnce()).cancel(mEventsToAlarmListener.get(
+                EVENT_STABILITY_CHECK_PASSED));
+    }
+
+    @Test
     public void testOnNonDdsSwitchBackToPrimary() {
+        // Disable Rat/SignalStrength based switch to test primary OOS based switch
+        doReturn(-1).when(mDataConfigManager).getAutoDataSwitchScoreTolerance();
+        mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(),
+                mPhoneSwitcher, mFeatureFlags, mMockedPhoneSwitcherCallback);
         doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
 
         prepareIdealUsesNonDdsCondition();
         // 1.1 service state changes - primary becomes available again, require validation
         serviceStateChanged(PHONE_1,
-                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING/*need validate*/);
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME/*need validate*/);
         processAllFutureMessages();
         verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
                 true/*needValidation*/);
@@ -193,7 +446,7 @@
         // 1.2 service state changes - secondary becomes unavailable, NO need validation
         serviceStateChanged(PHONE_1,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME/*need validate*/);
-        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING/*no need*/);
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_DENIED/*no need*/);
         processAllFutureMessages();
         // The later validation requirement overrides the previous
         verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
@@ -232,18 +485,48 @@
     }
 
     @Test
+    public void testOnNonDdsSwitchBackToPrimary_rat_signalStrength() {
+        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
+        doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
+
+        // 4.1 Display info and signal strength on secondary phone became bad just as the default
+        // Expect switch back since both phone has the same score.
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
+        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_POOR);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
+
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        prepareIdealUsesNonDdsCondition();
+        // 4.2 Display info and signal strength on secondary phone became worse than the default.
+        // Expect to switch.
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        signalStrengthChanged(PHONE_1, SignalStrength.SIGNAL_STRENGTH_GREAT);
+        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
+        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_POOR);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
+    }
+
+    @Test
     public void testCancelSwitch_onSecondary() {
         doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
         prepareIdealUsesNonDdsCondition();
 
-        // attempts the switch back due to secondary becomes ROAMING
-        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        // attempts the switch back due to secondary not usable
+        serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_DENIED);
         processAllFutureMessages();
 
         verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
                 false/*needValidation*/);
 
         // cancel the switch back attempt due to secondary back to HOME
+        clearInvocations(mMockedPhoneSwitcherCallback);
         serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         processAllFutureMessages();
 
@@ -251,6 +534,46 @@
     }
 
     @Test
+    public void testStabilityCheckOverride_basic() {
+        // Starting stability check for switching to non-DDS
+        prepareIdealUsesNonDdsCondition();
+        processAllMessages();
+
+        // Switch success, but the previous stability check is still pending
+        doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
+
+        // Display info and signal strength on secondary phone became worse than the default.
+        // Expect to switch back, and it should override the previous stability check
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        // process all messages include the delayed message
+        processAllFutureMessages();
+
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
+        verify(mMockedPhoneSwitcherCallback, never()).onRequireValidation(PHONE_2,
+                true/*needValidation*/);
+    }
+
+    @Test
+    public void testStabilityCheckOverride_uses_rat_signalStrength() {
+        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
+        // Switching due to availability first.
+        prepareIdealUsesNonDdsCondition();
+
+        // Verify stability check pending with short timer.
+        verify(mMockedPhoneSwitcherCallback, never()).onRequireValidation(anyInt(), anyBoolean());
+
+        // Switching due to performance now, should override to use long timer.
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        // Verify stability check pending with long timer.
+        assertThat(mAutoDataSwitchControllerUT.hasMessages(EVENT_STABILITY_CHECK_PASSED)).isFalse();
+        verify(mMockedAlarmManager).setExact(anyInt(), anyLong(), anyString(),
+                eq(mEventsToAlarmListener.get(
+                        EVENT_STABILITY_CHECK_PASSED)), any());
+    }
+
+    @Test
     public void testValidationFailedRetry() {
         prepareIdealUsesNonDdsCondition();
 
@@ -267,8 +590,10 @@
         // Change resource overlay
         doReturn(false).when(mDataConfigManager)
                 .isPingTestBeforeAutoDataSwitchRequired();
+        doReturn(-1 /*Disable signal based switch for easy mock*/).when(mDataConfigManager)
+                .getAutoDataSwitchScoreTolerance();
         mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(),
-                mPhoneSwitcher, mMockedPhoneSwitcherCallback);
+                mPhoneSwitcher, mFeatureFlags, mMockedPhoneSwitcherCallback);
 
         //1. DDS -> nDDS, verify callback doesn't require validation
         prepareIdealUsesNonDdsCondition();
@@ -278,7 +603,7 @@
 
         //2. nDDS -> DDS, verify callback doesn't require validation
         doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
-        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         processAllFutureMessages();
         verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
                 false/*needValidation*/);
@@ -327,13 +652,60 @@
                 eq(EVENT_SERVICE_STATE_CHANGED), eq(PHONE_2));
     }
 
+    @Test
+    public void testSubscriptionChangedUnregister() {
+        // Test single SIM loaded
+        int modemCount = 2;
+        doReturn(new int[]{SUB_2}).when(mSubscriptionManagerService)
+                .getActiveSubIdList(true);
+        mAutoDataSwitchControllerUT.notifySubscriptionsMappingChanged();
+        processAllMessages();
+
+        // Verify unregister from both slots since only 1 visible SIM is insufficient for switching
+        verify(mDisplayInfoController, times(modemCount))
+                .unregisterForTelephonyDisplayInfoChanged(any());
+        verify(mSignalStrengthController, times(modemCount))
+                .unregisterForSignalStrengthChanged(any());
+        verify(mSST, times(modemCount)).unregisterForServiceStateChanged(any());
+
+        // Test single -> Duel
+        clearInvocations(mDisplayInfoController, mSignalStrengthController, mSST);
+        doReturn(new int[]{SUB_1, SUB_2}).when(mSubscriptionManagerService)
+                .getActiveSubIdList(true);
+        mAutoDataSwitchControllerUT.notifySubscriptionsMappingChanged();
+        processAllMessages();
+
+        // Verify register on both slots
+        for (int phoneId = 0; phoneId < modemCount; phoneId++) {
+            verify(mDisplayInfoController).registerForTelephonyDisplayInfoChanged(any(),
+                    eq(EVENT_DISPLAY_INFO_CHANGED), eq(phoneId));
+            verify(mSignalStrengthController).registerForSignalStrengthChanged(any(),
+                    eq(EVENT_SIGNAL_STRENGTH_CHANGED), eq(phoneId));
+            verify(mSST).registerForServiceStateChanged(any(),
+                    eq(EVENT_SERVICE_STATE_CHANGED), eq(phoneId));
+        }
+    }
+
+    @Test
+    public void testRatSignalStrengthSkipEvaluation() {
+        // Verify the secondary phone is OOS and its score(0) is too low to justify the evaluation
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
+        processAllFutureMessages();
+        verify(mMockedPhoneSwitcherCallback, never())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedPhoneSwitcherCallback, never()).onRequireValidation(anyInt(), anyBoolean());
+    }
+
     /**
      * Trigger conditions
      * 1. service state changes
-     * 2. data setting changes
+     * 2. telephony display info changes
+     * 3. signal strength changes
+     * 4. data setting changes
      *      - user toggle data
      *      - user toggle auto switch feature
-     * 3. default network changes
+     * 5. default network changes
      *      - current network lost
      *      - network become active on non-cellular network
      */
@@ -343,16 +715,44 @@
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo
                 .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING);
 
-        // 2.1 User data enabled on primary SIM
-        doReturn(true).when(mPhone).isUserDataEnabled();
+        // 2. telephony display info changes
+        displayInfoChanged(PHONE_2, mGoodTelephonyDisplayInfo);
+        displayInfoChanged(PHONE_1, mBadTelephonyDisplayInfo);
 
-        // 2.2 Auto switch feature is enabled
+        // 3. signal strength changes
+        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_GREAT);
+        signalStrengthChanged(PHONE_1, SignalStrength.SIGNAL_STRENGTH_POOR);
+
+        // 4.1 User data enabled on primary SIM
+        doReturn(true).when(mPhone).isUserDataEnabled();
+        doReturn(true).when(mPhone).getDataRoamingEnabled();
+
+        // 4.2 Auto switch feature is enabled
+        doReturn(true).when(mPhone2).getDataRoamingEnabled();
         doReturn(true).when(mPhone2).isDataAllowed();
 
-        // 3.1 No default network
+        // 5. No default network
         mAutoDataSwitchControllerUT.updateDefaultNetworkCapabilities(null /*networkCapabilities*/);
     }
 
+    private void signalStrengthChanged(int phoneId, int level) {
+        SignalStrength ss = mock(SignalStrength.class);
+        doReturn(level).when(ss).getLevel();
+        doReturn(ss).when(mPhones[phoneId]).getSignalStrength();
+
+        Message msg = mAutoDataSwitchControllerUT.obtainMessage(EVENT_SIGNAL_STRENGTH_CHANGED);
+        msg.obj = new AsyncResult(phoneId, null, null);
+        mAutoDataSwitchControllerUT.sendMessage(msg);
+        processAllMessages();
+    }
+    private void displayInfoChanged(int phoneId, TelephonyDisplayInfo telephonyDisplayInfo) {
+        doReturn(telephonyDisplayInfo).when(mDisplayInfoController).getTelephonyDisplayInfo();
+
+        Message msg = mAutoDataSwitchControllerUT.obtainMessage(EVENT_DISPLAY_INFO_CHANGED);
+        msg.obj = new AsyncResult(phoneId, null, null);
+        mAutoDataSwitchControllerUT.sendMessage(msg);
+        processAllMessages();
+    }
     private void serviceStateChanged(int phoneId,
             @NetworkRegistrationInfo.RegistrationState int dataRegState) {
 
@@ -362,6 +762,7 @@
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setRegistrationState(dataRegState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
                 .build());
 
         ss.setDataRoamingFromRegistration(dataRegState
@@ -372,9 +773,19 @@
         Message msg = mAutoDataSwitchControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED);
         msg.obj = new AsyncResult(phoneId, null, null);
         mAutoDataSwitchControllerUT.sendMessage(msg);
+        processAllMessages();
     }
     private void setDefaultDataSubId(int defaultDataSub) {
         mDefaultDataSub = defaultDataSub;
         doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
     }
+
+    @Override
+    public void processAllFutureMessages() {
+        if (mFeatureFlags.autoDataSwitchRatSs()
+                && mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+            mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED).onAlarm();
+        }
+        super.processAllFutureMessages();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java
index 428699f..bbfd7a9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java
@@ -43,10 +43,11 @@
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataCallResponseTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataCallResponseTest.java
index 4b5189a..c0a9211 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataCallResponseTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataCallResponseTest.java
@@ -16,16 +16,20 @@
 
 package com.android.internal.telephony.data;
 
+import static org.junit.Assert.assertNotEquals;
+
 import android.net.InetAddresses;
 import android.net.LinkAddress;
 import android.os.Parcel;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.EpsQos;
 import android.telephony.data.Qos;
 import android.telephony.data.TrafficDescriptor;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -71,6 +75,8 @@
                 .setQosBearerSessions(new ArrayList<>())
                 .setTrafficDescriptors(
                         Arrays.asList(new TrafficDescriptor(FAKE_DNN, FAKE_OS_APP_ID)))
+                .setNetworkValidationStatus(
+                        PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED)
                 .build();
 
         Parcel p = Parcel.obtain();
@@ -100,6 +106,8 @@
                 .setMtuV6(1400)
                 .setTrafficDescriptors(
                         Arrays.asList(new TrafficDescriptor(FAKE_DNN, FAKE_OS_APP_ID)))
+                .setNetworkValidationStatus(
+                        PreciseDataConnectionState.NETWORK_VALIDATION_IN_PROGRESS)
                 .build();
 
         DataCallResponse response1 = new DataCallResponse.Builder()
@@ -119,6 +127,8 @@
                 .setMtuV6(1400)
                 .setTrafficDescriptors(
                         Arrays.asList(new TrafficDescriptor(FAKE_DNN, FAKE_OS_APP_ID)))
+                .setNetworkValidationStatus(
+                        PreciseDataConnectionState.NETWORK_VALIDATION_IN_PROGRESS)
                 .build();
 
         assertEquals(response, response);
@@ -145,11 +155,14 @@
                 .setTrafficDescriptors(Arrays.asList(
                         new TrafficDescriptor(FAKE_DNN, FAKE_OS_APP_ID),
                         new TrafficDescriptor(FAKE_DNN_2, FAKE_OS_APP_ID_2)))
+                .setNetworkValidationStatus(PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS)
                 .build();
 
         assertNotSame(response1, response2);
         assertNotSame(response1, null);
         assertNotSame(response1, new String[1]);
+        assertNotEquals(response1.getNetworkValidationStatus(),
+                response2.getNetworkValidationStatus());
         assertNotSame(response1.hashCode(), response2.hashCode());
 
         DataCallResponse response3 = new DataCallResponse.Builder()
@@ -172,9 +185,12 @@
                 .setTrafficDescriptors(Arrays.asList(
                         new TrafficDescriptor(FAKE_DNN_2, FAKE_OS_APP_ID_2),
                         new TrafficDescriptor(FAKE_DNN, FAKE_OS_APP_ID)))
+                .setNetworkValidationStatus(PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS)
                 .build();
 
         assertEquals(response2, response3);
+        assertEquals(response2.getNetworkValidationStatus(),
+                response3.getNetworkValidationStatus());
         assertEquals(response2.hashCode(), response3.hashCode());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
index d4a0804..005b312 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
@@ -18,18 +18,17 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
+import android.net.NetworkCapabilities;
 import android.os.Looper;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -51,8 +50,7 @@
         logd("DataConfigManagerTest +Setup!");
         super.setUp(getClass().getSimpleName());
         mBundle = mContextFixture.getCarrierConfigBundle();
-        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(mBundle);
-        mDataConfigManagerUT = new DataConfigManager(mPhone, Looper.myLooper());
+        mDataConfigManagerUT = new DataConfigManager(mPhone, Looper.myLooper(), mFeatureFlags);
         logd("DataConfigManagerTest -Setup!");
     }
 
@@ -130,19 +128,39 @@
                         TelephonyManager.NETWORK_TYPE_LTE,
                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, false/*isRoaming*/),
                 signalStrength)).isEqualTo(10227);
-        // Verify if entry contains any invalid negative scores, should yield -1.
+        // Verify if entry contains any invalid negative scores, should yield 0.
         doReturn(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN).when(signalStrength).getLevel();
         assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo(
                         TelephonyManager.NETWORK_TYPE_LTE,
                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/),
                 signalStrength))
-                .isEqualTo(-1/*INVALID_AUTO_DATA_SWITCH_SCORE*/);
+                .isEqualTo(0/*OUT_OF_SERVICE_AUTO_DATA_SWITCH_SCORE*/);
         // Verify non-existent entry should yield -1
         doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(signalStrength).getLevel();
         assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo(
                         TelephonyManager.NETWORK_TYPE_EDGE,
                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/),
                 signalStrength))
-                .isEqualTo(-1/*INVALID_AUTO_DATA_SWITCH_SCORE*/);
+                .isEqualTo(0/*OUT_OF_SERVICE_AUTO_DATA_SWITCH_SCORE*/);
+    }
+
+    @Test
+    public void testMeteredNetworkCapabilities() {
+        doReturn(true).when(mFeatureFlags).meteredEmbbUrlcc();
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[] {ApnSetting.TYPE_MMS_STRING, ApnSetting.TYPE_DEFAULT_STRING});
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
+                new String[] {ApnSetting.TYPE_SUPL_STRING, ApnSetting.TYPE_MCX_STRING});
+        mDataConfigManagerUT.sendEmptyMessage(1/*EVENT_CARRIER_CONFIG_CHANGED*/);
+        processAllMessages();
+
+        assertThat(mDataConfigManagerUT.getMeteredNetworkCapabilities(false)).containsExactly(
+                NetworkCapabilities.NET_CAPABILITY_MMS, NetworkCapabilities.NET_CAPABILITY_INTERNET,
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
+        assertThat(mDataConfigManagerUT.getMeteredNetworkCapabilities(true)).containsExactly(
+                NetworkCapabilities.NET_CAPABILITY_SUPL, NetworkCapabilities.NET_CAPABILITY_MCX,
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java
index 63f3e4f..8ce0894 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java
@@ -16,10 +16,10 @@
 
 package com.android.internal.telephony.data;
 
-import com.android.internal.telephony.TelephonyTest;
-
 import static com.google.common.truth.Truth.assertThat;
 
+import com.android.internal.telephony.TelephonyTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -58,5 +58,41 @@
         mDataEvaluationUT.removeDataDisallowedReason(
                 DataEvaluation.DataDisallowedReason.DATA_DISABLED);
         assertThat(mDataEvaluationUT.getDataDisallowedReasons().size()).isEqualTo(1);
+
+        mDataEvaluationUT.addDataDisallowedReason(
+                DataEvaluation.DataDisallowedReason.DATA_LIMIT_REACHED);
+        assertThat(mDataEvaluationUT.getDataDisallowedReasons().size()).isEqualTo(2);
+
+        mDataEvaluationUT.removeDataDisallowedReason(
+                DataEvaluation.DataDisallowedReason.ROAMING_DISABLED);
+        assertThat(mDataEvaluationUT.getDataDisallowedReasons().size()).isEqualTo(1);
     }
+
+
+    @Test
+    public void testIsSubsetOf() {
+        mDataEvaluationUT = new DataEvaluation(DataEvaluation.DataEvaluationReason.DATA_RETRY);
+        assertThat(mDataEvaluationUT.isSubsetOf(
+                DataEvaluation.DataDisallowedReason.ROAMING_DISABLED)).isTrue();
+        mDataEvaluationUT.addDataDisallowedReason(
+                DataEvaluation.DataDisallowedReason.DATA_DISABLED);
+        assertThat(mDataEvaluationUT.isSubsetOf(
+                DataEvaluation.DataDisallowedReason.DATA_DISABLED)).isTrue();
+        assertThat(mDataEvaluationUT.isSubsetOf(
+                DataEvaluation.DataDisallowedReason.ROAMING_DISABLED)).isFalse();
+    }
+
+    @Test
+    public void testContainsOnly() {
+        mDataEvaluationUT = new DataEvaluation(DataEvaluation.DataEvaluationReason.DATA_RETRY);
+        assertThat(mDataEvaluationUT.containsOnly(
+                DataEvaluation.DataDisallowedReason.ROAMING_DISABLED)).isFalse();
+        mDataEvaluationUT.addDataDisallowedReason(
+                DataEvaluation.DataDisallowedReason.DATA_DISABLED);
+        assertThat(mDataEvaluationUT.containsOnly(
+                DataEvaluation.DataDisallowedReason.DATA_DISABLED)).isTrue();
+        assertThat(mDataEvaluationUT.containsOnly(
+                DataEvaluation.DataDisallowedReason.ROAMING_DISABLED)).isFalse();
+    }
+
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataFailCauseTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataFailCauseTest.java
index 3590ae6..980762a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataFailCauseTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataFailCauseTest.java
@@ -26,7 +26,8 @@
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DataFailCause;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.TelephonyTest;
 
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 e49d304..a4598d3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -115,6 +115,7 @@
 import com.android.internal.telephony.data.DataNetworkController.HandoverRule;
 import com.android.internal.telephony.data.DataRetryManager.DataRetryManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.ims.ImsResolver;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 
@@ -149,6 +150,7 @@
     // Events
     private static final int EVENT_SIM_STATE_CHANGED = 9;
     private static final int EVENT_REEVALUATE_EXISTING_DATA_NETWORKS = 16;
+    private static final int EVENT_SERVICE_STATE_CHANGED = 17;
     private static final int EVENT_VOICE_CALL_ENDED = 18;
     private static final int EVENT_SUBSCRIPTION_OVERRIDE = 23;
 
@@ -158,7 +160,7 @@
     private DataNetworkControllerCallback mMockedDataNetworkControllerCallback;
     private DataRetryManagerCallback mMockedDataRetryManagerCallback;
     private ImsResolver mMockedImsResolver;
-
+    private DataStallRecoveryManager mMockedDataStallRecoveryManager;
     private ImsManager mMockedImsManager;
     private ImsMmTelManager mMockedImsMmTelManager;
     private ImsRcsManager mMockedImsRcsManager;
@@ -179,6 +181,10 @@
     private AccessNetworksManagerCallback mAccessNetworksManagerCallback;
     private LinkBandwidthEstimatorCallback mLinkBandwidthEstimatorCallback;
 
+    private boolean mIsNonTerrestrialNetwork = false;
+    private ArrayList<Integer> mCarrierSupportedSatelliteServices = new ArrayList<>();
+    private FeatureFlags mFeatureFlags;
+
     private final DataProfile mGeneralPurposeDataProfile = new DataProfile.Builder()
             .setApnSetting(new ApnSetting.Builder()
                     .setId(2163)
@@ -204,6 +210,8 @@
                     .setMaxConns(321)
                     .setWaitTime(456)
                     .setMaxConnsTime(789)
+                    .setInfrastructureBitmask(ApnSetting.INFRASTRUCTURE_SATELLITE
+                            | ApnSetting.INFRASTRUCTURE_CELLULAR)
                     .build())
             .setPreferred(false)
             .build();
@@ -385,6 +393,69 @@
                             "PRIORITIZE_LATENCY", 1).getBytes()))
             .build();
 
+    private final DataProfile mMmsOnWlanDataProfile = new DataProfile.Builder()
+            .setApnSetting(new ApnSetting.Builder()
+                    .setEntryName("mms_wlan")
+                    .setApnName("mms_wlan")
+                    .setApnTypeBitmask(ApnSetting.TYPE_MMS)
+                    .setCarrierEnabled(true)
+                    .setNetworkTypeBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN)
+                    .build())
+            .setPreferred(false)
+            .build();
+
+    private final DataProfile mNtnDataProfile = new DataProfile.Builder()
+            .setApnSetting(new ApnSetting.Builder()
+                    .setEntryName("ntn")
+                    .setApnName("ntn")
+                    .setApnTypeBitmask(ApnSetting.TYPE_RCS)
+                    .setCarrierEnabled(true)
+                    .setInfrastructureBitmask(ApnSetting.INFRASTRUCTURE_SATELLITE)
+                    .build())
+            .setPreferred(false)
+            .build();
+
+    private final DataProfile mEsimBootstrapDataProfile = new DataProfile.Builder()
+            .setApnSetting(new ApnSetting.Builder()
+                    .setEntryName("ESIM BOOTSTRAP")
+                    .setApnName("ESIM BOOTSTRAP")
+                    .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT)
+                    .setNetworkTypeBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                            | (int) TelephonyManager.NETWORK_TYPE_BITMASK_NR)
+                    .setCarrierEnabled(true)
+                    .setEsimBootstrapProvisioning(true)
+                    .build())
+            .setPreferred(false)
+            .build();
+
+    private final DataProfile mEsimBootstrapImsProfile = new DataProfile.Builder()
+            .setApnSetting(new ApnSetting.Builder()
+                    .setEntryName("IMS BOOTSTRAP")
+                    .setApnName("IMS BOOTSTRAP")
+                    .setApnTypeBitmask(ApnSetting.TYPE_IMS)
+                    .setNetworkTypeBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                            | (int) TelephonyManager.NETWORK_TYPE_BITMASK_NR)
+                    .setCarrierEnabled(true)
+                    .setEsimBootstrapProvisioning(true)
+                    .build())
+            .setPreferred(false)
+            .build();
+
+    private final DataProfile mEsimBootstrapRcsInfraStructureProfile =
+            new DataProfile.Builder()
+            .setApnSetting(new ApnSetting.Builder()
+                    .setEntryName("INFRASTRUCTURE BOOTSTRAP")
+                    .setApnName("INFRASTRUCTURE BOOTSTRAP")
+                    .setApnTypeBitmask(ApnSetting.TYPE_RCS)
+                    .setNetworkTypeBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                            | (int) TelephonyManager.NETWORK_TYPE_BITMASK_NR)
+                    .setCarrierEnabled(true)
+                    .setInfrastructureBitmask(2)
+                    .setEsimBootstrapProvisioning(true)
+                    .build())
+            .setPreferred(false)
+            .build();
+
     /** Data call response map. The first key is the transport type, the second key is the cid. */
     private final Map<Integer, Map<Integer, DataCallResponse>> mDataCallResponses = new HashMap<>();
 
@@ -601,7 +672,7 @@
         doReturn(ss).when(mSST).getServiceState();
         doReturn(ss).when(mPhone).getServiceState();
 
-        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+        mDataNetworkControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED).sendToTarget();
         processAllMessages();
     }
 
@@ -628,6 +699,8 @@
                 .setRegistrationState(dataRegState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
                 .setDataSpecificInfo(dsri)
+                .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
+                .setAvailableServices(mCarrierSupportedSatelliteServices)
                 .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
@@ -636,6 +709,8 @@
                 .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
                 .setRegistrationState(iwlanRegState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
+                .setAvailableServices(mCarrierSupportedSatelliteServices)
                 .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
@@ -722,9 +797,10 @@
                         "capabilities=eims, retry_interval=1000, maximum_retries=20",
                         "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|"
                                 + "-3|65543|65547|2252|2253|2254, retry_interval=2500",
-                        "capabilities=mms|supl|cbs, retry_interval=2000",
-                        "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
-                                + "5000|10000|15000|20000|40000|60000|120000|240000|"
+                        "capabilities=mms|supl|cbs|rcs, retry_interval=2000",
+                        "capabilities=internet|enterprise|dun|ims|fota|xcap|mcx|"
+                                + "prioritize_bandwidth|prioritize_latency, retry_interval="
+                                + "2500|3000|5000|10000|15000|20000|40000|60000|120000|240000|"
                                 + "600000|1200000|1800000, maximum_retries=20"
                 });
         mCarrierConfig.putStringArray(
@@ -754,6 +830,12 @@
         mCarrierConfig.putLongArray(CarrierConfigManager.KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY,
                 new long[] {180000, 180000, 180000, 180000});
 
+        mCarrierConfig.putLongArray(CarrierConfigManager.KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY,
+                new long[] {100, 100, 100, 100});
+        mCarrierConfig.putBooleanArray(
+                CarrierConfigManager.KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY,
+                new boolean[] {false, false, true, false, false});
+
         mContextFixture.putResource(com.android.internal.R.string.config_bandwidthEstimateSource,
                 "bandwidth_estimator");
 
@@ -777,9 +859,11 @@
         mMockedImsMmTelManager = Mockito.mock(ImsMmTelManager.class);
         mMockedImsRcsManager = Mockito.mock(ImsRcsManager.class);
         mMockedImsResolver = Mockito.mock(ImsResolver.class);
+        mMockedDataStallRecoveryManager = Mockito.mock(DataStallRecoveryManager.class);
         mMockedDataNetworkControllerCallback = Mockito.mock(DataNetworkControllerCallback.class);
         mMockedDataRetryManagerCallback = Mockito.mock(DataRetryManagerCallback.class);
         mMockSubInfo = Mockito.mock(SubscriptionInfo.class);
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
         when(mTelephonyComponentFactory.makeDataSettingsManager(any(Phone.class),
                 any(DataNetworkController.class), any(Looper.class),
                 any(DataSettingsManager.DataSettingsManagerCallback.class))).thenCallRealMethod();
@@ -861,7 +945,8 @@
         // to test, in this case, DataNetworkController. But since there are too many interactions
         // between DataNetworkController and its sub-modules, we intend to make those modules "real"
         // as well, except some modules below we replaced with mocks.
-        mDataNetworkControllerUT = new DataNetworkController(mPhone, Looper.myLooper());
+        mDataNetworkControllerUT = new DataNetworkController(mPhone, Looper.myLooper(),
+                mFeatureFlags);
         // First two come from DataServiceManager and the third comes from DataConfigManager which
         // is what we want to capture and assign to mCarrierConfigChangeListener
         verify(mCarrierConfigManager, times(3)).registerCarrierConfigChangeListener(any(),
@@ -888,6 +973,8 @@
         replaceInstance(DataNetworkController.class, "mAccessNetworksManager",
                 mDataNetworkControllerUT, mAccessNetworksManager);
         replaceInstance(ImsResolver.class, "sInstance", null, mMockedImsResolver);
+        replaceInstance(DataNetworkController.class, "mDataStallRecoveryManager",
+                mDataNetworkControllerUT, mMockedDataStallRecoveryManager);
 
         ArgumentCaptor<AccessNetworksManagerCallback> callbackCaptor =
                 ArgumentCaptor.forClass(AccessNetworksManagerCallback.class);
@@ -903,7 +990,9 @@
         List<DataProfile> profiles = List.of(mGeneralPurposeDataProfile,
                 mGeneralPurposeDataProfileAlternative, mImsCellularDataProfile,
                 mImsIwlanDataProfile, mEmergencyDataProfile, mFotaDataProfile,
-                mTetheringDataProfile);
+                mTetheringDataProfile, mMmsOnWlanDataProfile, mLowLatencyDataProfile,
+                mNtnDataProfile, mEsimBootstrapDataProfile,
+                mEsimBootstrapImsProfile, mEsimBootstrapRcsInfraStructureProfile);
 
         doAnswer(invocation -> {
             DataProfile dp = (DataProfile) invocation.getArguments()[0];
@@ -934,23 +1023,35 @@
             TelephonyNetworkRequest networkRequest =
                     (TelephonyNetworkRequest) invocation.getArguments()[0];
             int networkType = (int) invocation.getArguments()[1];
-            boolean ignorePermanentFailure = (boolean) invocation.getArguments()[2];
+            boolean isNtn = (boolean) invocation.getArguments()[2];
+            boolean isEsimBootstrapProvisioning = (boolean) invocation.getArguments()[3];
+            boolean ignorePermanentFailure = (boolean) invocation.getArguments()[4];
 
             for (DataProfile dataProfile : profiles) {
-                if (dataProfile.canSatisfy(networkRequest.getCapabilities())
-                        && (dataProfile.getApnSetting().getNetworkTypeBitmask() == 0
-                        || (dataProfile.getApnSetting().getNetworkTypeBitmask()
+                ApnSetting apnSetting = dataProfile.getApnSetting();
+                if (apnSetting != null
+                        && dataProfile.canSatisfy(networkRequest.getCapabilities())
+                        && (apnSetting.getNetworkTypeBitmask() == 0
+                        || (apnSetting.getNetworkTypeBitmask()
                         & ServiceState.getBitmaskForTech(networkType)) != 0)
-                        && (ignorePermanentFailure || (dataProfile.getApnSetting() != null
-                        && !dataProfile.getApnSetting().getPermanentFailed()))) {
+                        && (isEsimBootstrapProvisioning
+                        == apnSetting.isEsimBootstrapProvisioning())
+                        && ((isNtn && apnSetting.isForInfrastructure(
+                        ApnSetting.INFRASTRUCTURE_SATELLITE))
+                        || (!isNtn && apnSetting.isForInfrastructure(
+                        ApnSetting.INFRASTRUCTURE_CELLULAR)))
+                        && (ignorePermanentFailure || !apnSetting.getPermanentFailed())) {
                     return dataProfile;
                 }
             }
             logd("Cannot find data profile to satisfy " + networkRequest + ", network type="
-                    + TelephonyManager.getNetworkTypeName(networkType));
+                    + TelephonyManager.getNetworkTypeName(networkType) + ", ignorePermanentFailure="
+                    + ignorePermanentFailure + ", isNtn=" + isNtn + ","
+                    + "isEsimBootstrapProvisioning=" + isEsimBootstrapProvisioning);
             return null;
         }).when(mDataProfileManager).getDataProfileForNetworkRequest(
-                any(TelephonyNetworkRequest.class), anyInt(), anyBoolean());
+                any(TelephonyNetworkRequest.class), anyInt(), anyBoolean(), anyBoolean(),
+                anyBoolean());
 
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mAccessNetworksManager)
                 .getPreferredTransportByNetworkCapability(anyInt());
@@ -1120,7 +1221,7 @@
     }
 
     private void verifyInternetConnected() throws Exception {
-        verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkConnected(any());
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
     }
 
@@ -1158,6 +1259,18 @@
                 + dataNetworkList);
     }
 
+    private void verifyConnectedNetworkHasNoDataProfile(@NonNull DataProfile dataProfile)
+            throws Exception {
+        List<DataNetwork> dataNetworkList = getDataNetworks();
+        for (DataNetwork dataNetwork : getDataNetworks()) {
+            if (dataNetwork.isConnected() && dataNetwork.getDataProfile().equals(dataProfile)) {
+                fail("network with " + dataProfile + " is connected. dataNetworkList="
+                        + dataNetworkList);
+            }
+        }
+        return;
+    }
+
     private void verifyAllDataDisconnected() throws Exception {
         List<DataNetwork> dataNetworkList = getDataNetworks();
         assertWithMessage("All data should be disconnected but it's not. " + dataNetworkList)
@@ -1180,7 +1293,7 @@
                 InetAddresses.parseNumericAddress(IPV4_ADDRESS),
                 InetAddresses.parseNumericAddress(IPV6_ADDRESS));
 
-        verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkConnected(any());
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
     }
 
     @Test
@@ -1198,7 +1311,7 @@
                 InetAddresses.parseNumericAddress(IPV4_ADDRESS),
                 InetAddresses.parseNumericAddress(IPV6_ADDRESS));
 
-        verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkConnected(any());
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
 
         // database updated/reloaded, causing data profile id change
         List<DataProfile> profiles = List.of(mDuplicatedGeneralPurposeDataProfile);
@@ -1232,10 +1345,11 @@
                     + TelephonyManager.getNetworkTypeName(networkType));
             return null;
         }).when(mDataProfileManager).getDataProfileForNetworkRequest(
-                any(TelephonyNetworkRequest.class), anyInt(), anyBoolean());
+                any(TelephonyNetworkRequest.class), anyInt(), anyBoolean(), anyBoolean(),
+                anyBoolean());
 
         // verify the network still connects
-        verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkConnected(any());
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
 
         // A NOT_VCN_MANAGED request cannot be satisfied by the existing network, but will adopt the
         // same data profile
@@ -1246,7 +1360,7 @@
         processAllMessages();
 
         // verify the network still connects
-        verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkConnected(any());
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
         // verify we don't try to setup a separate network for the not_vcn_managed request
         dataNetworkList = getDataNetworks();
         assertThat(dataNetworkList).hasSize(1);
@@ -1277,7 +1391,7 @@
                 createDataCallResponse(1, DataCallResponse.LINK_STATUS_ACTIVE, tdList));
         doReturn(mEnterpriseDataProfile).when(mDataProfileManager)
                 .getDataProfileForNetworkRequest(any(TelephonyNetworkRequest.class), anyInt(),
-                        anyBoolean());
+                        anyBoolean(), anyBoolean(), anyBoolean());
 
         NetworkCapabilities netCaps = new NetworkCapabilities();
         netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE);
@@ -1309,7 +1423,7 @@
         mDataNetworkControllerUT.addNetworkRequest(request);
         processAllMessages();
         verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(true));
-        verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkConnected(any());
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
 
         int countOfCallbacks = dataNetworkControllerCallbacks.size();
 
@@ -1332,7 +1446,8 @@
         processAllMessages();
         verifyAllDataDisconnected();
         verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
-        verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkDisconnected();
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(
+                eq(Collections.emptySet()));
         verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged(
                 eq(DataCallResponse.LINK_STATUS_INACTIVE));
     }
@@ -1491,12 +1606,13 @@
         // Now RAT changes from UMTS to GSM
         doReturn(null).when(mDataProfileManager).getDataProfileForNetworkRequest(
                 any(TelephonyNetworkRequest.class), eq(TelephonyManager.NETWORK_TYPE_GSM),
-                anyBoolean());
+                anyBoolean(), anyBoolean(), anyBoolean());
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_GSM,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         verifyAllDataDisconnected();
         verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
-        verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkDisconnected();
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(
+                eq(Collections.emptySet()));
         verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged(
                 eq(DataCallResponse.LINK_STATUS_INACTIVE));
 
@@ -1504,14 +1620,14 @@
         // Now RAT changes from GSM to UMTS
         doReturn(null).when(mDataProfileManager).getDataProfileForNetworkRequest(
                 any(TelephonyNetworkRequest.class), eq(TelephonyManager.NETWORK_TYPE_UMTS),
-                anyBoolean());
+                anyBoolean(), anyBoolean(), anyBoolean());
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_UMTS,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
 
         doReturn(mGeneralPurposeDataProfile).when(mDataProfileManager)
                 .getDataProfileForNetworkRequest(any(TelephonyNetworkRequest.class), anyInt(),
-                        anyBoolean());
+                        anyBoolean(), anyBoolean(), anyBoolean());
         // Now RAT changes from UMTS to LTE
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
@@ -1585,6 +1701,74 @@
     }
 
     @Test
+    public void testNonTerrestrialNetworkChangedWithoutDataSupport() throws Exception {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mIsNonTerrestrialNetwork = true;
+        // Data is not supported while using satellite
+        mCarrierSupportedSatelliteServices.add(NetworkRegistrationInfo.SERVICE_TYPE_VOICE);
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+
+        // Data with internet capability should not be allowed
+        // when the device is using non-terrestrial network
+        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
+        mIsNonTerrestrialNetwork = false;
+        mCarrierSupportedSatelliteServices.clear();
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        // Verify data is restored.
+        verifyInternetConnected();
+    }
+
+    @Test
+    public void testNonTerrestrialNetworkWithDataSupport() throws Exception {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mIsNonTerrestrialNetwork = true;
+        // Data is supported while using satellite
+        mCarrierSupportedSatelliteServices.add(NetworkRegistrationInfo.SERVICE_TYPE_DATA);
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+
+        // Verify data is connected.
+        verifyInternetConnected();
+
+        mIsNonTerrestrialNetwork = false;
+        mCarrierSupportedSatelliteServices.clear();
+    }
+
+    @Test
+    public void testNonTerrestrialNetworkWithFlagDisabled() throws Exception {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
+
+        mIsNonTerrestrialNetwork = true;
+        // Data is not supported while using satellite
+        mCarrierSupportedSatelliteServices.add(NetworkRegistrationInfo.SERVICE_TYPE_VOICE);
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+
+        // As feature is disabled, data is connected.
+        verifyInternetConnected();
+
+        mIsNonTerrestrialNetwork = false;
+        mCarrierSupportedSatelliteServices.clear();
+    }
+
+
+    @Test
     public void testRoamingDataChanged() throws Exception {
         doReturn(true).when(mServiceState).getDataRoaming();
 
@@ -1608,7 +1792,6 @@
 
         // Verify data is restored.
         verifyInternetConnected();
-        Mockito.clearInvocations(mMockedDataNetworkControllerCallback);
 
         // Roaming data disabled
         mDataNetworkControllerUT.getDataSettingsManager().setDataRoamingEnabled(false);
@@ -1616,6 +1799,7 @@
 
         // Verify data is torn down.
         verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        Mockito.clearInvocations(mMockedDataNetworkControllerCallback);
 
         // Registration is back to HOME.
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
@@ -1774,9 +1958,13 @@
 
     @Test
     public void testIsDataEnabledOverriddenForApnDataDuringCall() throws Exception {
-        doReturn(1).when(mPhone).getSubId();
+        // Assume phone2 is the default data phone
+        Phone phone2 = Mockito.mock(Phone.class);
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, phone2});
         doReturn(2).when(mSubscriptionManagerService).getDefaultDataSubId();
-        // Data disabled
+        doReturn(1).when(mPhone).getSubId();
+
+        // Data disabled on nonDDS
         mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
                 TelephonyManager.DATA_ENABLED_REASON_USER, false, mContext.getOpPackageName());
 
@@ -1796,6 +1984,8 @@
 
         // Phone ringing
         doReturn(PhoneConstants.State.RINGING).when(mPhone).getState();
+        // Data is user enabled on DDS
+        doReturn(true).when(phone2).isUserDataEnabled();
         mDataNetworkControllerUT.addNetworkRequest(
                 createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
         processAllMessages();
@@ -2429,8 +2619,7 @@
                 new String[]{"source=EUTRAN, target=IWLAN, type=disallowed, capabilities=MMS|IMS",
                         "source=IWLAN, target=EUTRAN, type=disallowed, capabilities=MMS"});
         // Force data config manager to reload the carrier config.
-        mDataNetworkControllerUT.getDataConfigManager().obtainMessage(
-                1/*EVENT_CARRIER_CONFIG_CHANGED*/).sendToTarget();
+        carrierConfigChanged();
         processAllMessages();
 
         testSetupImsDataNetwork();
@@ -2471,6 +2660,36 @@
     }
 
     @Test
+    public void testHandoverDataNetworkNotAllowedByPolicyDelayDueToVoiceCall() throws Exception {
+        doReturn(true).when(mFeatureFlags).relaxHoTeardown();
+        // Config delay IMS tear down enabled
+        mCarrierConfig.putBoolean(CarrierConfigManager.KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL,
+                true);
+        mCarrierConfig.putStringArray(CarrierConfigManager.KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY,
+                new String[]{"source=EUTRAN, target=IWLAN, type=disallowed, capabilities=MMS|IMS"});
+        carrierConfigChanged();
+
+        testSetupImsDataNetwork();
+
+        // Ringing an active call, should delay handover tear down
+        doReturn(PhoneConstants.State.RINGING).when(mCT).getState();
+        updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // Verify network is still connected due to active voice call
+        verify(mMockedWwanDataServiceManager, never()).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
+
+        // Verify tear down after call ends
+        doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
+        mDataNetworkControllerUT.obtainMessage(EVENT_VOICE_CALL_ENDED).sendToTarget();
+        processAllFutureMessages();
+
+        verify(mMockedWwanDataServiceManager).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
+    }
+
+    @Test
     public void testHandoverDataNetworkNotAllowedByRoamingPolicy() throws Exception {
         mCarrierConfig.putStringArray(CarrierConfigManager.KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY,
                 new String[]{"source=EUTRAN|NGRAN|IWLAN, target=EUTRAN|NGRAN|IWLAN, roaming=true, "
@@ -3216,7 +3435,7 @@
         processAllMessages();
 
         verify(mMockedDataNetworkControllerCallback)
-                .onInternetDataNetworkConnected(any());
+                .onConnectedInternetDataNetworksChanged(any());
         List<DataNetwork> dataNetworks = getDataNetworks();
         assertThat(dataNetworks).hasSize(1);
         assertThat(dataNetworks.get(0).getNetworkCapabilities().hasCapability(
@@ -3450,7 +3669,7 @@
                 createDataCallResponse(1, DataCallResponse.LINK_STATUS_ACTIVE, tdList));
         doReturn(mEnterpriseDataProfile).when(mDataProfileManager)
                 .getDataProfileForNetworkRequest(any(TelephonyNetworkRequest.class), anyInt(),
-                        anyBoolean());
+                        anyBoolean(), anyBoolean(), anyBoolean());
 
         NetworkCapabilities netCaps = new NetworkCapabilities();
         netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE);
@@ -3494,8 +3713,12 @@
 
     @Test
     public void testNonVoPStoVoPSImsSetup() throws Exception {
-        // Even allow lingering when NoVops, should have no effect on NoVops -> Vops
-        mCarrierConfig.putBoolean(CarrierConfigManager.Ims.KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL, true);
+        doReturn(true).when(mFeatureFlags).allowMmtelInNonVops();
+        mDataNetworkControllerUT.getDataSettingsManager().setDataRoamingEnabled(true);
+        // Config that allows non-vops bring up when Roaming
+        mCarrierConfig.putIntArray(CarrierConfigManager.Ims
+                .KEY_IMS_PDN_ENABLED_IN_NO_VOPS_SUPPORT_INT_ARRAY, new int[]
+                {CarrierConfigManager.Ims.NETWORK_TYPE_ROAMING});
         carrierConfigChanged();
 
         // VOPS not supported
@@ -3515,6 +3738,30 @@
         processAllMessages();
         verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
 
+        // Verify bring up in Home is not allowed.
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
+        processAllMessages();
+        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
+        // Verify bring up in Roaming is allowed.
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING, dsri);
+        processAllMessages();
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_IMS,
+                NetworkCapabilities.NET_CAPABILITY_MMTEL);
+
+        // Verify the roaming network survives network re-evaluation.
+        mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)
+                .sendToTarget();
+        processAllMessages();
+
+        // Service state changed to Home, non-vops area is no longer allowed
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
+        processAllMessages();
+        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
         // VoPS supported
         dsri = new DataSpecificRegistrationInfo.Builder(8)
                 .setNrAvailable(true)
@@ -3778,6 +4025,50 @@
     }
 
     @Test
+    public void testNoGracefulTearDownForEmergencyDataNetwork() throws Exception {
+        setImsRegistered(true);
+
+        mCarrierConfig.putStringArray(CarrierConfigManager.KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY,
+                new String[]{"source=EUTRAN, target=IWLAN, type=disallowed, capabilities=EIMS|IMS",
+                        "source=IWLAN, target=EUTRAN, type=disallowed, capabilities=MMS"});
+        // Force data config manager to reload the carrier config.
+        carrierConfigChanged();
+        processAllMessages();
+
+        // setup emergency data network.
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
+        netCaps.setRequestorPackageName(FAKE_MMTEL_PACKAGE);
+
+        NetworkRequest nativeNetworkRequest = new NetworkRequest(netCaps,
+                ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId, NetworkRequest.Type.REQUEST);
+        TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(
+                nativeNetworkRequest, mPhone);
+
+        mDataNetworkControllerUT.addNetworkRequest(networkRequest);
+        processAllMessages();
+
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_EIMS);
+
+        updateTransport(NetworkCapabilities.NET_CAPABILITY_EIMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // Verify all data disconnected.
+        verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
+        verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged(
+                eq(DataCallResponse.LINK_STATUS_INACTIVE));
+
+        // A new data network should be connected on IWLAN
+        List<DataNetwork> dataNetworkList = getDataNetworks();
+        assertThat(dataNetworkList).hasSize(1);
+        assertThat(dataNetworkList.get(0).isConnected()).isTrue();
+        assertThat(dataNetworkList.get(0).getNetworkCapabilities().hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_EIMS)).isTrue();
+        assertThat(dataNetworkList.get(0).getTransport())
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+    }
+
+    @Test
     public void testNetworkRequestRemovedBeforeRetry() {
         setFailedSetupDataResponse(mMockedWwanDataServiceManager, DataFailCause.CONGESTION,
                 DataCallResponse.RETRY_DURATION_UNDEFINED, false);
@@ -4090,8 +4381,6 @@
 
     @Test
     public void testHandoverDataNetworkNonVops() throws Exception {
-        ServiceState ss = new ServiceState();
-
         DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
                 .setNrAvailable(true)
                 .setEnDcAvailable(true)
@@ -4099,36 +4388,13 @@
                         LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
                         LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
                 .build();
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .setDataSpecificInfo(dsri)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
-                .build());
-        processServiceStateRegStateForTest(ss);
-        doReturn(ss).when(mSST).getServiceState();
-        doReturn(ss).when(mPhone).getServiceState();
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
                 .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
 
-        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+        mDataNetworkControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED).sendToTarget();
         processAllMessages();
 
         mDataNetworkControllerUT.addNetworkRequest(
@@ -4157,8 +4423,6 @@
         mCarrierConfig.putBoolean(CarrierConfigManager.Ims.KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL, true);
         carrierConfigChanged();
 
-        ServiceState ss = new ServiceState();
-
         DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
                 .setNrAvailable(true)
                 .setEnDcAvailable(true)
@@ -4166,36 +4430,13 @@
                         LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
                         LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
                 .build();
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .setDataSpecificInfo(dsri)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
-                .build());
-        processServiceStateRegStateForTest(ss);
-        doReturn(ss).when(mSST).getServiceState();
-        doReturn(ss).when(mPhone).getServiceState();
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
                 .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
 
-        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+        mDataNetworkControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED).sendToTarget();
         processAllMessages();
 
         mDataNetworkControllerUT.addNetworkRequest(
@@ -4221,8 +4462,6 @@
 
     @Test
     public void testNonMmtelImsHandoverDataNetworkNonVops() throws Exception {
-        ServiceState ss = new ServiceState();
-
         DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
                 .setNrAvailable(true)
                 .setEnDcAvailable(true)
@@ -4231,35 +4470,13 @@
                         LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
                 .build();
 
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .setDataSpecificInfo(dsri)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
-                .build());
-        processServiceStateRegStateForTest(ss);
-        doReturn(ss).when(mSST).getServiceState();
-        doReturn(ss).when(mPhone).getServiceState();
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
                 .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
 
-        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+        mDataNetworkControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED).sendToTarget();
         processAllMessages();
 
         // Bring up the IMS network that does not require MMTEL
@@ -4267,7 +4484,7 @@
                 createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS));
         processAllMessages();
 
-        // Even though the network request does not have MMTEL, but the network support it, so
+        // Even though the network request does not have MMTEL, the WLAN network support it, so
         // the network capabilities should still have MMTEL.
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_IMS,
                 NetworkCapabilities.NET_CAPABILITY_MMTEL);
@@ -4281,9 +4498,9 @@
                 any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
                 any(), any(), anyBoolean(), any(Message.class));
 
-        // The IMS network should still have IMS and MMTEL.
+        // The IMS network should still have IMS, but MMTEL is removed.
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_IMS);
-        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_MMTEL);
+        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
     }
 
     @Test
@@ -4292,8 +4509,6 @@
         mCarrierConfig.putBoolean(CarrierConfigManager.Ims.KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL, true);
         carrierConfigChanged();
 
-        ServiceState ss = new ServiceState();
-
         // VoPS network
         DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
                 .setNrAvailable(true)
@@ -4303,35 +4518,13 @@
                         LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
                 .build();
 
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .setDataSpecificInfo(dsri)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
-                .build());
-        processServiceStateRegStateForTest(ss);
-        doReturn(ss).when(mSST).getServiceState();
-        doReturn(ss).when(mPhone).getServiceState();
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mAccessNetworksManager)
                 .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
 
-        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+        mDataNetworkControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED).sendToTarget();
         processAllMessages();
 
         // Bring up the IMS network that does require MMTEL
@@ -4344,7 +4537,6 @@
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_IMS,
                 NetworkCapabilities.NET_CAPABILITY_MMTEL);
 
-        ss = new ServiceState();
         // Non VoPS network
         dsri = new DataSpecificRegistrationInfo.Builder(8)
                 .setNrAvailable(true)
@@ -4354,32 +4546,10 @@
                         LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
                 .build();
 
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .setDataSpecificInfo(dsri)
-                .build());
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
-                .build());
-        processServiceStateRegStateForTest(ss);
-        doReturn(ss).when(mSST).getServiceState();
-        doReturn(ss).when(mPhone).getServiceState();
-
-        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+        mDataNetworkControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED).sendToTarget();
         processAllMessages();
 
         // The IMS network is alive due to KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL = true
@@ -4463,7 +4633,8 @@
         processAllMessages();
         verifyAllDataDisconnected();
         verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
-        verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkDisconnected();
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(
+                eq(Collections.emptySet()));
         verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged(
                 eq(DataCallResponse.LINK_STATUS_INACTIVE));
     }
@@ -4582,7 +4753,7 @@
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
                 NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, null, true);
         doReturn(ss).when(mSST).getServiceState();
-        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+        mDataNetworkControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED).sendToTarget();
         mDataNetworkControllerUT.removeNetworkRequest(request);
         mDataNetworkControllerUT.addNetworkRequest(request);
         processAllMessages();
@@ -4604,7 +4775,7 @@
                 createDataCallResponse(1, DataCallResponse.LINK_STATUS_ACTIVE, tdList));
         doReturn(mEnterpriseDataProfile).when(mDataProfileManager)
                 .getDataProfileForNetworkRequest(any(TelephonyNetworkRequest.class), anyInt(),
-                        anyBoolean());
+                        anyBoolean(), anyBoolean(), anyBoolean());
         mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
@@ -4642,7 +4813,7 @@
                 createDataCallResponse(2, DataCallResponse.LINK_STATUS_ACTIVE, tdList));
         doReturn(mLowLatencyDataProfile).when(mDataProfileManager)
                 .getDataProfileForNetworkRequest(any(TelephonyNetworkRequest.class), anyInt(),
-                        anyBoolean());
+                        anyBoolean(), anyBoolean(), anyBoolean());
         processAllFutureMessages();
 
         dataNetworkList = getDataNetworks();
@@ -4657,4 +4828,293 @@
         assertThat(dataNetworkList.get(1).getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)).isTrue();
     }
+
+    @Test
+    public void testAllowBringUpWithDifferentDataProfileForWlan() throws Exception {
+        // Mock MMS preferred on WLAN
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+
+        // Setup a default cellular network that's capable of MMS
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS));
+        processAllMessages();
+        verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile);
+
+        // Mock the designated MMS profile when WLAN is preferred
+        doReturn(mMmsOnWlanDataProfile).when(mDataProfileManager).getDataProfileForNetworkRequest(
+                any(TelephonyNetworkRequest.class), eq(TelephonyManager.NETWORK_TYPE_IWLAN),
+                anyBoolean(), anyBoolean(), anyBoolean());
+        setSuccessfulSetupDataResponse(mMockedWlanDataServiceManager,
+                createDataCallResponse(2, DataCallResponse.LINK_STATUS_ACTIVE));
+
+        // Verify the designated MMS profile is used to satisfy MMS request
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_MMS));
+        processAllMessages();
+        verifyConnectedNetworkHasDataProfile(mMmsOnWlanDataProfile);
+    }
+
+    @Test
+    public void testNonTerrestrialNetwork() throws Exception {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mIsNonTerrestrialNetwork = true;
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_RCS));
+        processAllMessages();
+        verifyConnectedNetworkHasDataProfile(mNtnDataProfile);
+    }
+
+    @Test
+    public void testIsEsimBootStrapProvisioningActivatedWithFlagEnabledAndProvisioningClass() {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+
+        assertThat(mDataNetworkControllerUT.isEsimBootStrapProvisioningActivated()).isTrue();
+    }
+
+    @Test
+    public void testIsEsimBootStrapProvisioningActivatedWithFlagEnabledAndNoProvisioningClass() {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_UNSET).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+
+        assertThat(mDataNetworkControllerUT.isEsimBootStrapProvisioningActivated()).isFalse();
+    }
+
+    @Test
+    public void testIsEsimBootStrapProvisioningActivatedWithFlagDisabledAndNoProvisioningClass() {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(false);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_UNSET).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+
+        assertThat(mDataNetworkControllerUT.isEsimBootStrapProvisioningActivated()).isFalse();
+    }
+
+    @Test
+    public void testIsEsimBootStrapProvisioningActivatedWithFlagDisabledAndProvisioningClass() {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(false);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+
+        assertThat(mDataNetworkControllerUT.isEsimBootStrapProvisioningActivated()).isFalse();
+    }
+
+    @Test
+    public void testNetworkOnProvisioningProfileClass_WithFlagEnabled() throws Exception {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        // Allowed data limit Unlimited
+        mContextFixture.putIntResource(com.android.internal.R.integer
+                .config_esim_bootstrap_data_limit_bytes, -1);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapDataProfile);
+
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS,
+                        NetworkCapabilities.NET_CAPABILITY_MMTEL));
+        setSuccessfulSetupDataResponse(mMockedDataServiceManagers
+                .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), 2);
+        processAllMessages();
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapImsProfile);
+
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_RCS));
+        processAllMessages();
+        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
+    }
+
+    @Test
+    public void testSetUpPdn_WithBootStrapDataLimit_Zero() throws Exception {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        // Allowed data limit set as zero
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        // With current consumed bytes is zero, same as allowed limit, data_limit_reached
+        // disallowed reason is met
+        verifyConnectedNetworkHasNoDataProfile(mEsimBootstrapDataProfile);
+
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS,
+                        NetworkCapabilities.NET_CAPABILITY_MMTEL));
+        processAllMessages();
+        // New network request also meets with data limit reached disallowed reason
+        verifyConnectedNetworkHasNoDataProfile(mEsimBootstrapImsProfile);
+    }
+
+    @Test
+    public void testSetUpPdn_WithBootStrapDataLimit_Unlimited() throws Exception {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        // Allowed data limit
+        mContextFixture.putIntResource(com.android.internal.R.integer
+                 .config_esim_bootstrap_data_limit_bytes, -1/*unlimited*/);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        // With allowed data limit unlimited, connection is allowed
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapDataProfile);
+
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS,
+                        NetworkCapabilities.NET_CAPABILITY_MMTEL));
+        setSuccessfulSetupDataResponse(mMockedDataServiceManagers
+                .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), 2);
+        processAllMessages();
+        // With allowed data limit unlimited, connection is allowed
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapImsProfile);
+
+        // Both internet and IMS should be retained after network re-evaluation
+        mDataNetworkControllerUT.obtainMessage(16 /*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/,
+                DataEvaluation.DataEvaluationReason.CHECK_DATA_USAGE).sendToTarget();
+        processAllMessages();
+        // With allowed data limit unlimited, connection is allowed
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapDataProfile);
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapImsProfile);
+    }
+
+    @Test
+    public void testNetworkOnNonProvisioningProfileClass_WithFlagEnabled() throws Exception {
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_UNSET).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        verifyConnectedNetworkHasNoDataProfile(mEsimBootstrapDataProfile);
+
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS,
+                        NetworkCapabilities.NET_CAPABILITY_MMTEL));
+        setSuccessfulSetupDataResponse(mMockedDataServiceManagers
+                .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), 2);
+        processAllMessages();
+        verifyConnectedNetworkHasNoDataProfile(mEsimBootstrapImsProfile);
+    }
+
+    @Test
+    public void testNtnNetworkOnProvisioningProfileClass_WithFlagEnabled() throws Exception {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        // Allowed data limit Unlimited
+        mContextFixture.putIntResource(com.android.internal.R.integer
+                .config_esim_bootstrap_data_limit_bytes, -1);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+        mIsNonTerrestrialNetwork = true;
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_RCS));
+        processAllMessages();
+
+        assertThat(mDataNetworkControllerUT.isEsimBootStrapProvisioningActivated()).isTrue();
+        verifyConnectedNetworkHasNoDataProfile(mNtnDataProfile);
+        verifyConnectedNetworkHasDataProfile(mEsimBootstrapRcsInfraStructureProfile);
+    }
+
+    @Test
+    public void testNonNtnNetworkOnProvisioningProfileClass_WithFlagEnabled() throws Exception {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
+        doReturn(new SubscriptionInfoInternal.Builder().setId(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_RCS));
+        processAllMessages();
+
+        assertThat(mDataNetworkControllerUT.isEsimBootStrapProvisioningActivated()).isTrue();
+        verifyConnectedNetworkHasNoDataProfile(mNtnDataProfile);
+        verifyConnectedNetworkHasNoDataProfile(mEsimBootstrapRcsInfraStructureProfile);
+    }
+
+    @Test
+    public void testRequestNetworkValidationWithConnectedNetwork() throws Exception {
+        // IMS preferred on IWLAN.
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(
+                        eq(NetworkCapabilities.NET_CAPABILITY_IMS));
+
+        // Request IMS
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS,
+                        NetworkCapabilities.NET_CAPABILITY_MMTEL));
+        setSuccessfulSetupDataResponse(mMockedDataServiceManagers
+                .get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN), 3);
+        processAllMessages();
+
+        // Make sure IMS on IWLAN.
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_IMS);
+        DataNetwork dataNetwork = getDataNetworks().get(0);
+        assertThat(dataNetwork.getTransport()).isEqualTo(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        mDataNetworkControllerUT.requestNetworkValidation(NetworkCapabilities.NET_CAPABILITY_IMS,
+                mIntegerConsumer);
+        processAllMessages();
+        assertThat(waitForIntegerConsumerResponse(1 /*numOfEvents*/)).isFalse();
+    }
+
+    @Test
+    public void testRequestNetworkValidationWithNoConnectedNetwork()
+            throws Exception {
+        // IMS preferred on IWLAN.
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(
+                        eq(NetworkCapabilities.NET_CAPABILITY_IMS));
+
+        // IMS On Wlan not connected
+        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
+        //Connected List is Empty
+        mDataNetworkControllerUT.requestNetworkValidation(NetworkCapabilities.NET_CAPABILITY_IMS,
+                mIntegerConsumer);
+        processAllMessages();
+        assertThat(waitForIntegerConsumerResponse(1 /*numOfEvents*/)).isTrue();
+        assertThat(mIntegerConsumerResult).isEqualTo(DataServiceCallback.RESULT_ERROR_INVALID_ARG);
+    }
+
+    @Test
+    public void testRequestNetworkValidationWithInvalidArg() {
+        mDataNetworkControllerUT.requestNetworkValidation(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY,
+                mIntegerConsumer);
+        processAllMessages();
+        assertThat(waitForIntegerConsumerResponse(1 /*numOfEvents*/)).isTrue();
+        assertThat(mIntegerConsumerResult).isEqualTo(DataServiceCallback.RESULT_ERROR_INVALID_ARG);
+    }
 }
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 41a714c..83c583b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.net.ConnectivityManager;
 import android.net.InetAddresses;
@@ -39,6 +40,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
+import android.net.NetworkScore;
 import android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener;
 import android.net.vcn.VcnNetworkPolicyResult;
 import android.os.AsyncResult;
@@ -63,7 +65,10 @@
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
 import android.telephony.data.DataServiceCallback;
+import android.telephony.data.EpsQos;
 import android.telephony.data.NetworkSliceInfo;
+import android.telephony.data.Qos;
+import android.telephony.data.QosBearerSession;
 import android.telephony.data.TrafficDescriptor;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -72,10 +77,12 @@
 
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataEvaluation.DataAllowedReason;
 import com.android.internal.telephony.data.DataNetwork.DataNetworkCallback;
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
+import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
 
@@ -92,7 +99,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Executor;
-
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class DataNetworkTest extends TelephonyTest {
@@ -119,7 +125,7 @@
             .setApnName("fake_apn")
             .setUser("user")
             .setPassword("passwd")
-            .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL)
+            .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL | ApnSetting.TYPE_MMS)
             .setProtocol(ApnSetting.PROTOCOL_IPV6)
             .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
             .setCarrierEnabled(true)
@@ -130,6 +136,18 @@
             .setMaxConnsTime(789)
             .build();
 
+    private final ApnSetting mMmsApnSetting = new ApnSetting.Builder()
+            .setId(2164)
+            .setOperatorNumeric("12345")
+            .setEntryName("fake_mms_apn")
+            .setApnName("fake_mms_apn")
+            .setApnTypeBitmask(ApnSetting.TYPE_MMS)
+            .setProtocol(ApnSetting.PROTOCOL_IPV6)
+            .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
+            .setCarrierEnabled(true)
+            .setNetworkTypeBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN)
+            .build();
+
     private final ApnSetting mImsApnSetting = new ApnSetting.Builder()
             .setId(2163)
             .setOperatorNumeric("12345")
@@ -153,6 +171,11 @@
             .setTrafficDescriptor(new TrafficDescriptor("fake_apn", null))
             .build();
 
+    private final DataProfile mMmsDataProfile = new DataProfile.Builder()
+            .setApnSetting(mMmsApnSetting)
+            .setTrafficDescriptor(new TrafficDescriptor("fake_apn", null))
+            .build();
+
     private final DataProfile mImsDataProfile = new DataProfile.Builder()
             .setApnSetting(mImsApnSetting)
             .setTrafficDescriptor(new TrafficDescriptor("fake_apn", null))
@@ -204,6 +227,11 @@
             .setTrafficDescriptor(new TrafficDescriptor(null, null))
             .build();
 
+    private final Qos mDefaultQos = new EpsQos(
+            new Qos.QosBandwidth(1000, 1),
+            new Qos.QosBandwidth(1000, 0),
+            9 /* QCI */);
+
     // Mocked classes
     private DataNetworkCallback mDataNetworkCallback;
     private DataCallSessionStats mDataCallSessionStats;
@@ -216,16 +244,22 @@
                     .build();
 
     private void setSuccessfulSetupDataResponse(DataServiceManager dsm, int cid) {
-        setSuccessfulSetupDataResponse(dsm, cid, Collections.emptyList());
+        setSuccessfulSetupDataResponse(dsm, cid, Collections.emptyList(), null);
     }
 
     private void setSuccessfulSetupDataResponse(DataServiceManager dsm, int cid,
             List<TrafficDescriptor> tds) {
+        setSuccessfulSetupDataResponse(dsm, cid, tds, null);
+    }
+
+    private void setSuccessfulSetupDataResponse(DataServiceManager dsm, int cid,
+            List<TrafficDescriptor> tds, Qos defaultQos) {
         doAnswer(invocation -> {
             final Message msg = (Message) invocation.getArguments()[10];
 
             DataCallResponse response = createDataCallResponse(
-                    cid, DataCallResponse.LINK_STATUS_ACTIVE, tds);
+                    cid, DataCallResponse.LINK_STATUS_ACTIVE, tds, defaultQos,
+                    PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
             msg.getData().putParcelable("data_call_response", response);
             msg.arg1 = DataServiceCallback.RESULT_SUCCESS;
             msg.sendToTarget();
@@ -236,7 +270,7 @@
     }
 
     private DataCallResponse createDataCallResponse(int cid, int linkStatus,
-            List<TrafficDescriptor> tds) {
+            List<TrafficDescriptor> tds, Qos defaultQos, int validationStatus) {
         return new DataCallResponse.Builder()
                 .setCause(0)
                 .setRetryDurationMillis(-1L)
@@ -260,6 +294,8 @@
                 .setPduSessionId(1)
                 .setQosBearerSessions(new ArrayList<>())
                 .setTrafficDescriptors(tds)
+                .setDefaultQos(defaultQos)
+                .setNetworkValidationStatus(validationStatus)
                 .build();
     }
 
@@ -410,10 +446,11 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build(), mPhone));
 
-        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
+        setSuccessfulSetupDataResponse(
+                mMockedWwanDataServiceManager, 123, Collections.emptyList(), mDefaultQos);
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mInternetDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 DataAllowedReason.NORMAL, mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -470,6 +507,7 @@
         assertThat(pdcsList.get(0).getTransportType())
                 .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
         assertThat(pdcsList.get(0).getLinkProperties()).isEqualTo(new LinkProperties());
+        assertThat(pdcsList.get(0).getDefaultQos()).isEqualTo(null);
 
         assertThat(pdcsList.get(1).getApnSetting()).isEqualTo(mInternetApnSetting);
         assertThat(pdcsList.get(1).getState()).isEqualTo(TelephonyManager.DATA_CONNECTED);
@@ -477,6 +515,7 @@
         assertThat(pdcsList.get(1).getNetworkType()).isEqualTo(TelephonyManager.NETWORK_TYPE_LTE);
         assertThat(pdcsList.get(1).getTransportType())
                 .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        assertThat(pdcsList.get(1).getDefaultQos()).isEqualTo(mDefaultQos);
         assertThat(pdcsList.get(1).getLinkProperties().getAddresses().get(0))
                 .isEqualTo(InetAddresses.parseNumericAddress(IPV4_ADDRESS));
         assertThat(pdcsList.get(1).getLinkProperties().getAddresses().get(1))
@@ -519,8 +558,8 @@
 
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mInternetDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 DataAllowedReason.NORMAL, mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -615,8 +654,8 @@
                 .build(), mPhone));
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mInternetDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 DataAllowedReason.NORMAL, mDataNetworkCallback);
         processAllMessages();
@@ -634,20 +673,7 @@
 
     @Test
     public void testCreateImsDataNetwork() throws Exception {
-        NetworkRequestList networkRequestList = new NetworkRequestList();
-        networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                .build(), mPhone));
-
-        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
-
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mImsDataProfile, networkRequestList,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
-                DataAllowedReason.NORMAL, mDataNetworkCallback);
-        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
-                mDataNetworkUT, mDataCallSessionStats);
-        processAllMessages();
+        createImsDataNetwork(true/*isMmtel*/);
 
         ArgumentCaptor<LinkProperties> linkPropertiesCaptor =
                 ArgumentCaptor.forClass(LinkProperties.class);
@@ -668,7 +694,7 @@
                 eq(DataCallResponse.PDU_SESSION_ID_NOT_SET), nullable(NetworkSliceInfo.class),
                 any(TrafficDescriptor.class), eq(true), any(Message.class));
         assertThat(mDataNetworkUT.getId()).isEqualTo(123);
-        assertThat(networkRequestList.get(0).getState())
+        assertThat(mDataNetworkUT.getAttachedNetworkRequestList().get(0).getState())
                 .isEqualTo(TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
         LinkProperties lp = mDataNetworkUT.getLinkProperties();
         assertThat(lp.getAddresses()).containsExactly(
@@ -724,8 +750,8 @@
         );
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123, tds);
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mEnterpriseDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mEnterpriseDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL,
                 mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -758,8 +784,8 @@
         );
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123, tds);
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mUrlccDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mUrlccDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL,
                 mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -791,8 +817,8 @@
         );
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123, tds);
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mEmbbDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mEmbbDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL,
                 mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -825,8 +851,8 @@
 
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123, tds);
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mCbsDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mCbsDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL,
                 mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -857,8 +883,8 @@
                         .getBytes())
         );
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mCbsDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mCbsDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL,
                 mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -939,9 +965,10 @@
 
         setSuccessfulSetupDataResponse(mMockedWlanDataServiceManager, 123);
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mImsDataProfile, networkRequestList, AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
-                DataAllowedReason.NORMAL, mDataNetworkCallback);
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mImsDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, DataAllowedReason.NORMAL,
+                mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
                 mDataNetworkUT, mDataCallSessionStats);
         processAllMessages();
@@ -1029,6 +1056,9 @@
         setupDataNetwork();
 
         setSuccessfulSetupDataResponse(mMockedWlanDataServiceManager, 456);
+        TelephonyNetworkAgent mockNetworkAgent = Mockito.mock(TelephonyNetworkAgent.class);
+        replaceInstance(DataNetwork.class, "mNetworkAgent",
+                mDataNetworkUT, mockNetworkAgent);
         // Now handover to IWLAN
         mDataNetworkUT.startHandover(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, null);
         processAllMessages();
@@ -1053,6 +1083,18 @@
                 .isEqualTo(TelephonyManager.DATA_HANDOVER_IN_PROGRESS);
         assertThat(pdcsList.get(3).getState()).isEqualTo(TelephonyManager.DATA_CONNECTED);
 
+        ArgumentCaptor<NetworkScore> networkScoreCaptor =
+                ArgumentCaptor.forClass(NetworkScore.class);
+        verify(mockNetworkAgent, times(2))
+                .sendNetworkScore(networkScoreCaptor.capture());
+        List<NetworkScore> networkScoreList = networkScoreCaptor.getAllValues();
+
+        assertThat(networkScoreList).hasSize(2);
+        assertThat(networkScoreList.get(0).getKeepConnectedReason())
+                .isEqualTo(NetworkScore.KEEP_CONNECTED_FOR_HANDOVER);
+        assertThat(networkScoreList.get(1).getKeepConnectedReason())
+                .isEqualTo(NetworkScore.KEEP_CONNECTED_NONE);
+
         // Now handover back to cellular
         Mockito.clearInvocations(mDataNetworkCallback);
         Mockito.clearInvocations(mLinkBandwidthEstimator);
@@ -1072,6 +1114,9 @@
     public void testHandoverFailed() throws Exception {
         setupDataNetwork();
 
+        TelephonyNetworkAgent mockNetworkAgent = Mockito.mock(TelephonyNetworkAgent.class);
+        replaceInstance(DataNetwork.class, "mNetworkAgent",
+                mDataNetworkUT, mockNetworkAgent);
         setFailedSetupDataResponse(mMockedWlanDataServiceManager,
                 DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE);
         // Now attempt to handover to IWLAN but fail it.
@@ -1101,6 +1146,18 @@
         assertThat(pdcsList.get(3).getLastCauseCode())
                 .isEqualTo(DataFailCause.SERVICE_TEMPORARILY_UNAVAILABLE);
 
+        ArgumentCaptor<NetworkScore> networkScoreCaptor =
+                ArgumentCaptor.forClass(NetworkScore.class);
+        verify(mockNetworkAgent, times(2))
+                .sendNetworkScore(networkScoreCaptor.capture());
+        List<NetworkScore> networkScoreList = networkScoreCaptor.getAllValues();
+
+        assertThat(networkScoreList).hasSize(2);
+        assertThat(networkScoreList.get(0).getKeepConnectedReason())
+                .isEqualTo(NetworkScore.KEEP_CONNECTED_FOR_HANDOVER);
+        assertThat(networkScoreList.get(1).getKeepConnectedReason())
+                .isEqualTo(NetworkScore.KEEP_CONNECTED_NONE);
+
         // Test source PDN lost during the HO, expect tear down after HO
         setFailedSetupDataResponse(mMockedWlanDataServiceManager,
                 DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE);
@@ -1131,8 +1188,8 @@
 
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mInternetDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 DataAllowedReason.NORMAL, mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -1218,6 +1275,88 @@
     }
 
     @Test
+    public void testRestrictedNetworkDataEnabled() throws Exception {
+        NetworkRequestList networkRequestList = new NetworkRequestList();
+        // Restricted network request
+        networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build(), mPhone));
+
+        // Data disabled
+        doReturn(false).when(mDataSettingsManager).isDataEnabled();
+        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
+
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                DataAllowedReason.RESTRICTED_REQUEST, mDataNetworkCallback);
+        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
+                mDataNetworkUT, mDataCallSessionStats);
+        mDataNetworkUT.sendMessage(18/*EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED*/,
+                new AsyncResult(null, new int[]{ADMIN_UID1, ADMIN_UID2}, null));
+        processAllMessages();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isFalse();
+
+        ArgumentCaptor<DataSettingsManagerCallback> dataSettingsManagerCallbackCaptor =
+                ArgumentCaptor.forClass(DataSettingsManagerCallback.class);
+        verify(mDataSettingsManager).registerCallback(dataSettingsManagerCallbackCaptor.capture());
+
+        // Data enabled
+        doReturn(true).when(mDataSettingsManager).isDataEnabled();
+        dataSettingsManagerCallbackCaptor.getValue().onDataEnabledChanged(true,
+                TelephonyManager.DATA_ENABLED_REASON_USER, "");
+
+        // Network should become unrestricted.
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
+    }
+
+    @Test
+    public void testRestrictedNetworkDataRoamingEnabled() throws Exception {
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+
+        NetworkRequestList networkRequestList = new NetworkRequestList();
+        // Restricted network request
+        networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build(), mPhone));
+
+        // Data roaming disabled
+        doReturn(false).when(mDataSettingsManager).isDataRoamingEnabled();
+        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
+
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                DataAllowedReason.RESTRICTED_REQUEST, mDataNetworkCallback);
+        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
+                mDataNetworkUT, mDataCallSessionStats);
+        mDataNetworkUT.sendMessage(18/*EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED*/,
+                new AsyncResult(null, new int[]{ADMIN_UID1, ADMIN_UID2}, null));
+        processAllMessages();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isFalse();
+
+        ArgumentCaptor<DataSettingsManagerCallback> dataSettingsManagerCallbackCaptor =
+                ArgumentCaptor.forClass(DataSettingsManagerCallback.class);
+        verify(mDataSettingsManager).registerCallback(dataSettingsManagerCallbackCaptor.capture());
+
+        // Data roaming enabled
+        doReturn(true).when(mDataSettingsManager).isDataRoamingEnabled();
+        dataSettingsManagerCallbackCaptor.getValue().onDataRoamingEnabledChanged(true);
+
+        // Network should become unrestricted.
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
+    }
+
+    @Test
     public void testVcnPolicyUpdated() throws Exception {
         setupDataNetwork();
 
@@ -1243,8 +1382,25 @@
 
     @Test
     public void testVcnPolicyTeardownRequested() throws Exception {
-        setupDataNetwork();
+        // 1. Tear down in Connecting state
+        doAnswer(invocation -> {
+            NetworkCapabilities nc = invocation.getArgument(0);
 
+            return new VcnNetworkPolicyResult(
+                    true /* isTearDownRequested */, nc);
+        }).when(mVcnManager).applyVcnNetworkPolicy(any(), any());
+        setupDataNetwork();
+        verify(mMockedWwanDataServiceManager).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
+
+        // 2. Tear down in Connected state
+        doAnswer(invocation -> {
+            NetworkCapabilities nc = invocation.getArgument(0);
+
+            return new VcnNetworkPolicyResult(
+                    false /* isTearDownRequested */, nc);
+        }).when(mVcnManager).applyVcnNetworkPolicy(any(), any());
+        setupDataNetwork();
         doAnswer(invocation -> {
             NetworkCapabilities nc = invocation.getArgument(0);
 
@@ -1281,7 +1437,7 @@
 
     @Test
     public void testNetworkAgentConfig() throws Exception {
-        testCreateImsDataNetwork();
+        createImsDataNetwork(false/*IsMmtel*/);
 
         ArgumentCaptor<NetworkAgentConfig> captor = ArgumentCaptor
                 .forClass(NetworkAgentConfig.class);
@@ -1307,8 +1463,8 @@
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build(), mPhone));
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mInternetDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 DataAllowedReason.NORMAL, mDataNetworkCallback);
         NetworkCapabilities caps = mDataNetworkUT.getNetworkCapabilities();
@@ -1400,7 +1556,8 @@
     }
 
     @Test
-    public void testMovingToNonVops() throws Exception {
+    public void testMovingToNonVopsRequestedMmtel() throws Exception {
+        createImsDataNetwork(true/*isMmtel*/);
         DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
                 .setNrAvailable(true)
                 .setEnDcAvailable(true)
@@ -1410,7 +1567,6 @@
                 .build();
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
-        testCreateImsDataNetwork();
 
         assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue();
@@ -1431,6 +1587,37 @@
     }
 
     @Test
+    public void testNonMmtelImsMovingToNonVops() throws Exception {
+        createImsDataNetwork(false/*isMmtel*/);
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                .build();
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue();
+
+        dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
+                .build();
+        logd("Trigger non VoPS");
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
+        // MMTEL should be removed to reflect the actual Vops status.
+        assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_MMTEL)).isFalse();
+    }
+
+    @Test
     public void testCssIndicatorChanged() throws Exception {
         setupDataNetwork();
 
@@ -1661,8 +1848,8 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build(), mPhone));
 
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                mInternetDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 DataAllowedReason.NORMAL, mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -1804,8 +1991,8 @@
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build(), mPhone));
-        mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers,
-                m5gDataProfile, networkRequestList,
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, m5gDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL,
                 mDataNetworkCallback);
         replaceInstance(DataNetwork.class, "mDataCallSessionStats",
@@ -1831,7 +2018,8 @@
 
         // data state updated
         DataCallResponse response = createDataCallResponse(123,
-                DataCallResponse.LINK_STATUS_DORMANT, Collections.emptyList());
+                DataCallResponse.LINK_STATUS_DORMANT, Collections.emptyList(), null,
+                PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
         mDataNetworkUT.sendMessage(8 /*EVENT_DATA_STATE_CHANGED*/, new AsyncResult(
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, List.of(response), null));
         processAllMessages();
@@ -1851,4 +2039,266 @@
         verify(mDataNetworkCallback).onLinkStatusChanged(eq(mDataNetworkUT),
                 eq(DataCallResponse.LINK_STATUS_INACTIVE));
     }
+
+    @Test
+    public void testHandleDataNetworkValidationRequestOnDataConnectedState() throws Exception {
+        setupIwlanDataNetwork();
+
+        // First Request
+        mDataNetworkUT.requestNetworkValidation(mIntegerConsumer);
+        processAllMessages();
+        verify(mMockedWlanDataServiceManager).requestNetworkValidation(eq(123 /*cid*/),
+                any(Message.class));
+
+        // Duplicate Request
+        mDataNetworkUT.requestNetworkValidation(mIntegerConsumer);
+        processAllMessages();
+        verify(mMockedWlanDataServiceManager).requestNetworkValidation(eq(123 /*cid*/),
+                any(Message.class));
+        assertThat(waitForIntegerConsumerResponse(1 /*numOfEvents*/)).isTrue();
+        assertThat(mIntegerConsumerResult).isEqualTo(DataServiceCallback.RESULT_ERROR_BUSY);
+    }
+
+    @Test
+    public void testHandleDataNetworkValidationRequestResultCode() throws Exception {
+        setupDataNetwork();
+        mDataNetworkUT.requestNetworkValidation(mIntegerConsumer);
+        processAllMessages();
+
+        mDataNetworkUT.sendMessage(29 /*EVENT_DATA_NETWORK_VALIDATION_RESPONSE*/,
+                DataServiceCallback.RESULT_SUCCESS);
+        processAllMessages();
+
+        assertThat(waitForIntegerConsumerResponse(1 /*numOfEvents*/)).isTrue();
+        assertThat(mIntegerConsumerResult).isEqualTo(DataServiceCallback.RESULT_SUCCESS);
+
+        //mNetworkValidationResultCodeCallback null case
+        mIntegerConsumerResult = DataServiceCallback.RESULT_ERROR_UNSUPPORTED;
+        mDataNetworkUT.sendMessage(29 /*EVENT_DATA_NETWORK_VALIDATION_RESPONSE*/,
+                DataServiceCallback.RESULT_SUCCESS);
+        processAllMessages();
+        assertThat(waitForIntegerConsumerResponse(1 /*numOfEvents*/)).isFalse();
+        assertThat(mIntegerConsumerResult).isNotEqualTo(DataServiceCallback.RESULT_SUCCESS);
+    }
+
+    @Test
+    public void testHandleDataNetworkValidationRequestNotConnectedState() throws Exception {
+        NetworkRequestList networkRequestList = new NetworkRequestList();
+        NetworkRequest.Builder builder = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
+        networkRequestList.add(new TelephonyNetworkRequest(builder.build(), mPhone));
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mImsDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                DataAllowedReason.NORMAL, mDataNetworkCallback);
+        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
+                mDataNetworkUT, mDataCallSessionStats);
+        processAllMessages();
+
+        // Data Not connected State
+        mDataNetworkUT.requestNetworkValidation(mIntegerConsumer);
+        processAllMessages();
+
+        assertThat(waitForIntegerConsumerResponse(1 /*numOfEvents*/)).isTrue();
+        assertThat(mIntegerConsumerResult)
+                .isEqualTo(DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+    }
+
+    @Test
+    public void testValidationStatusOnPreciseDataConnectionState_FlagEnabled() throws Exception {
+        when(mFeatureFlags.networkValidation()).thenReturn(true);
+        setupIwlanDataNetwork();
+
+        ArgumentCaptor<PreciseDataConnectionState> pdcsCaptor =
+                ArgumentCaptor.forClass(PreciseDataConnectionState.class);
+        verify(mPhone, times(2)).notifyDataConnection(pdcsCaptor.capture());
+        List<PreciseDataConnectionState> pdcsList = pdcsCaptor.getAllValues();
+
+        assertThat(pdcsList.get(1).getApnSetting()).isEqualTo(mImsApnSetting);
+        assertThat(pdcsList.get(1).getState()).isEqualTo(TelephonyManager.DATA_CONNECTED);
+        assertThat(pdcsList.get(1).getNetworkType()).isEqualTo(TelephonyManager.NETWORK_TYPE_IWLAN);
+        assertThat(pdcsList.get(1).getTransportType())
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        assertThat(pdcsList.get(1).getNetworkValidationStatus())
+                .isEqualTo(PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
+
+        // Request Network Validation
+        mDataNetworkUT.requestNetworkValidation(mIntegerConsumer);
+        processAllMessages();
+        verify(mMockedWlanDataServiceManager).requestNetworkValidation(eq(123 /*cid*/),
+                any(Message.class));
+
+        // data state updated with network validation status
+        DataCallResponse response = createDataCallResponse(123,
+                DataCallResponse.LINK_STATUS_ACTIVE, Collections.emptyList(), null,
+                PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS);
+        mDataNetworkUT.sendMessage(8 /*EVENT_DATA_STATE_CHANGED*/, new AsyncResult(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, List.of(response), null));
+        processAllMessages();
+
+        // Verify updated validation status at precise data connection state
+        pdcsCaptor = ArgumentCaptor.forClass(PreciseDataConnectionState.class);
+        verify(mPhone, times(3)).notifyDataConnection(pdcsCaptor.capture());
+
+
+        // data state updated with same network validation status
+        response = createDataCallResponse(123,
+                DataCallResponse.LINK_STATUS_ACTIVE, Collections.emptyList(), null,
+                PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS);
+        mDataNetworkUT.sendMessage(8 /*EVENT_DATA_STATE_CHANGED*/, new AsyncResult(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, List.of(response), null));
+        processAllMessages();
+
+        // Verify precise data connection state not posted again
+        pdcsCaptor = ArgumentCaptor.forClass(PreciseDataConnectionState.class);
+        verify(mPhone, times(3)).notifyDataConnection(pdcsCaptor.capture());
+    }
+
+    @Test
+    public void testValidationStatus_FlagDisabled() throws Exception {
+        // network validation flag disabled
+        when(mFeatureFlags.networkValidation()).thenReturn(false);
+        setupIwlanDataNetwork();
+
+        // precise data connection state posted for setup data call response
+        ArgumentCaptor<PreciseDataConnectionState> pdcsCaptor =
+                ArgumentCaptor.forClass(PreciseDataConnectionState.class);
+        verify(mPhone, times(2)).notifyDataConnection(pdcsCaptor.capture());
+
+        // data state updated with network validation status
+        DataCallResponse response = createDataCallResponse(123,
+                DataCallResponse.LINK_STATUS_ACTIVE, Collections.emptyList(), null,
+                PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS);
+        mDataNetworkUT.sendMessage(8 /*EVENT_DATA_STATE_CHANGED*/, new AsyncResult(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, List.of(response), null));
+        processAllMessages();
+
+        // Verify updated validation status at precise data connection state not posted due to flag
+        // disabled
+        pdcsCaptor = ArgumentCaptor.forClass(PreciseDataConnectionState.class);
+        verify(mPhone, times(2)).notifyDataConnection(pdcsCaptor.capture());
+        List<PreciseDataConnectionState> pdcsList = pdcsCaptor.getAllValues();
+        assertThat(pdcsList.get(1).getNetworkValidationStatus())
+                .isEqualTo(PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
+    }
+
+    private void setupIwlanDataNetwork() throws Exception {
+        // setup iwlan data network over ims
+        doReturn(mIwlanNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                eq(NetworkRegistrationInfo.DOMAIN_PS),
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+
+        NetworkRequestList networkRequestList = new NetworkRequestList();
+        networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                .build(), mPhone));
+        setSuccessfulSetupDataResponse(mMockedWlanDataServiceManager, 123);
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mImsDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                DataAllowedReason.NORMAL, mDataNetworkCallback);
+        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
+                mDataNetworkUT, mDataCallSessionStats);
+        processAllMessages();
+    }
+
+    private void createImsDataNetwork(boolean isMmtel) throws Exception {
+        NetworkRequestList networkRequestList = new NetworkRequestList();
+        NetworkRequest.Builder builder = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+        if (isMmtel) builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
+        networkRequestList.add(new TelephonyNetworkRequest(builder.build(), mPhone));
+
+        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
+
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mImsDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                DataAllowedReason.NORMAL, mDataNetworkCallback);
+        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
+                mDataNetworkUT, mDataCallSessionStats);
+        processAllMessages();
+    }
+
+    @Test
+    public void testMmsCapabilityRemovedWhenMmsPreferredOnIwlan() throws Exception {
+        doReturn(true).when(mFeatureFlags).forceIwlanMms();
+        setupDataNetwork();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isTrue();
+
+        ArgumentCaptor<AccessNetworksManagerCallback> accessNetworksManagerCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(AccessNetworksManagerCallback.class);
+        verify(mAccessNetworksManager).registerCallback(
+                accessNetworksManagerCallbackArgumentCaptor.capture());
+
+        // Now QNS prefers MMS on IWLAN
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+        doReturn(mMmsDataProfile).when(mDataProfileManager).getDataProfileForNetworkRequest(
+                any(TelephonyNetworkRequest.class),
+                    eq(TelephonyManager.NETWORK_TYPE_IWLAN), eq(false), eq(false), eq(false));
+        accessNetworksManagerCallbackArgumentCaptor.getValue()
+                .onPreferredTransportChanged(NetworkCapabilities.NET_CAPABILITY_MMS);
+        processAllMessages();
+
+        // Check if MMS capability is removed.
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isFalse();
+
+        // Now QNS prefers MMS on IWLAN
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mAccessNetworksManager)
+            .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+        accessNetworksManagerCallbackArgumentCaptor.getValue()
+                .onPreferredTransportChanged(NetworkCapabilities.NET_CAPABILITY_MMS);
+        processAllMessages();
+
+        // Check if MMS capability is added back.
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isTrue();
+    }
+
+    @Test
+    public void testQosSessionsChanged()  throws Exception {
+        createImsDataNetwork(false/*isMmtel*/);
+        List<QosBearerSession> newQosSessions =
+                List.of(new QosBearerSession(1, mDefaultQos, Collections.emptyList()));
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setRetryDurationMillis(-1L)
+                .setId(123)
+                .setLinkStatus(DataCallResponse.LINK_STATUS_ACTIVE)
+                .setProtocolType(ApnSetting.PROTOCOL_IPV4V6)
+                .setInterfaceName("ifname")
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(IPV4_ADDRESS), 32),
+                        new LinkAddress(IPV6_ADDRESS + "/64")))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress("10.0.2.3"),
+                        InetAddresses.parseNumericAddress("fd00:976a::9")))
+                .setGatewayAddresses(Arrays.asList(
+                        InetAddresses.parseNumericAddress("10.0.2.15"),
+                        InetAddresses.parseNumericAddress("fe80::2")))
+                .setPcscfAddresses(Arrays.asList(
+                        InetAddresses.parseNumericAddress("fd00:976a:c305:1d::8"),
+                        InetAddresses.parseNumericAddress("fd00:976a:c202:1d::7"),
+                        InetAddresses.parseNumericAddress("fd00:976a:c305:1d::5")))
+                .setMtuV4(1234)
+                .setPduSessionId(1)
+                .setQosBearerSessions(newQosSessions)
+                .setTrafficDescriptors(Collections.emptyList())
+                .setDefaultQos(mDefaultQos)
+                .build();
+
+        // Qos sessions list changed
+        mDataNetworkUT.obtainMessage(8/*EVENT_DATA_STATE_CHANGED*/,
+                new AsyncResult(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        List.of(response), null)).sendToTarget();
+        processAllMessages();
+
+        verify(mDataNetworkCallback).onQosSessionsChanged(newQosSessions);
+    }
 }
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 feb51d0..44d207d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.content.ContentValues;
@@ -65,6 +66,7 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -80,8 +82,13 @@
     private static final String IMS_APN = "IMS_APN";
     private static final String TETHERING_APN = "DUN_APN";
     private static final String APN_SET_ID_1_APN = "APN_SET_ID_1_APN";
+    private static final String RCS_APN = "RCS_APN";
+    private static final String RCS_APN1 = "RCS_APN1";
     private static final String APN_SET_ID_1_TETHERING_APN = "APN_SET_ID_1_TETHERING_APN";
     private static final String MATCH_ALL_APN_SET_ID_IMS_APN = "MATCH_ALL_APN_SET_ID_IMS_APN";
+    private static final String ESIM_BOOTSTRAP_PROVISIONING_APN = "ESIM_BOOTSTRAP_PROVISIONING_APN";
+    private static final String TEST_BOOTSTRAP_APN =
+            "TEST_BOOTSTRAP_APN";
     private static final String PLMN = "330123";
     private static final int DEFAULT_APN_SET_ID = Telephony.Carriers.NO_APN_SET_ID;
     private static final int APN_SET_ID_1 = 1;
@@ -131,6 +138,9 @@
                 Telephony.Carriers.CARRIER_ID,
                 Telephony.Carriers.SKIP_464XLAT,
                 Telephony.Carriers.ALWAYS_ON,
+                Telephony.Carriers.INFRASTRUCTURE_BITMASK,
+                Telephony.Carriers.ESIM_BOOTSTRAP_PROVISIONING,
+                Telephony.Carriers.EDITED_STATUS
         };
 
         private int mPreferredApnSet = 0;
@@ -169,7 +179,10 @@
                         DEFAULT_APN_SET_ID,     // apn_set_id
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
-                        0                       // always_on
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 // default internet data profile for RAT CDMA, to test update preferred data profile
                 new Object[]{
@@ -204,7 +217,10 @@
                         DEFAULT_APN_SET_ID,     // apn_set_id
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
-                        0                       // always_on
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         2,                      // id
@@ -238,7 +254,10 @@
                         DEFAULT_APN_SET_ID,     // apn_set_id
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
-                        0                       // always_on
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         3,                      // id
@@ -272,7 +291,10 @@
                         DEFAULT_APN_SET_ID,     // apn_set_id
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
-                        0                       // alwys_on
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         4,                      // id
@@ -307,7 +329,10 @@
                         DEFAULT_APN_SET_ID,     // apn_set_id
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
-                        0                       // always_on
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 // This APN entry is created to test de-duping.
                 new Object[]{
@@ -343,7 +368,10 @@
                         DEFAULT_APN_SET_ID,     // apn_set_id
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
-                        0                       // always_on
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         6,                      // id
@@ -378,7 +406,10 @@
                         APN_SET_ID_1,           // apn_set_id
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
-                        0                       // always_on
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         7,                      // id
@@ -413,7 +444,10 @@
                         APN_SET_ID_1,           // apn_set_id
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
-                        0                       // always_on
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         8,                      // id
@@ -448,7 +482,200 @@
                         MATCH_ALL_APN_SET_ID,   // apn_set_id
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
-                        0                       // always_on
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
+                },
+                new Object[]{
+                        9,                      // id
+                        PLMN,                   // numeric
+                        RCS_APN,                // name
+                        RCS_APN,                // apn
+                        "",                     // proxy
+                        "",                     // port
+                        "",                     // mmsc
+                        "",                     // mmsproxy
+                        "",                     // mmsport
+                        "",                     // user
+                        "",                     // password
+                        -1,                     // authtype
+                        "rcs",                  // types
+                        "IPV4V6",               // protocol
+                        "IPV4V6",               // roaming_protocol
+                        1,                      // carrier_enabled
+                        0,                      // profile_id
+                        1,                      // modem_cognitive
+                        0,                      // max_conns
+                        0,                      // wait_time
+                        0,                      // max_conns_time
+                        0,                      // mtu
+                        1280,                   // mtu_v4
+                        1280,                   // mtu_v6
+                        "",                     // mvno_type
+                        "",                     // mnvo_match_data
+                        TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                                | TelephonyManager.NETWORK_TYPE_BITMASK_NR, // network_type_bitmask
+                        0,                      // lingering_network_type_bitmask
+                        DEFAULT_APN_SET_ID,     // apn_set_id
+                        -1,                     // carrier_id
+                        -1,                     // skip_464xlat
+                        0,                      // always_on
+                        2,                      // INFRASTRUCTURE_SATELLITE
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
+                },
+                new Object[]{
+                        10,                     // id
+                        PLMN,                   // numeric
+                        ESIM_BOOTSTRAP_PROVISIONING_APN, // name
+                        ESIM_BOOTSTRAP_PROVISIONING_APN, // apn
+                        "",                     // proxy
+                        "",                     // port
+                        "",                     // mmsc
+                        "",                     // mmsproxy
+                        "",                     // mmsport
+                        "",                     // user
+                        "",                     // password
+                        -1,                     // authtype
+                        "default,supl",         // types
+                        "IPV4V6",               // protocol
+                        "IPV4V6",               // roaming_protocol
+                        1,                      // carrier_enabled
+                        0,                      // profile_id
+                        1,                      // modem_cognitive
+                        0,                      // max_conns
+                        0,                      // wait_time
+                        0,                      // max_conns_time
+                        0,                      // mtu
+                        1280,                   // mtu_v4
+                        1280,                   // mtu_v6
+                        "",                     // mvno_type
+                        "",                     // mnvo_match_data
+                        TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                                | TelephonyManager.NETWORK_TYPE_BITMASK_NR, // network_type_bitmask
+                        0,                      // lingering_network_type_bitmask
+                        MATCH_ALL_APN_SET_ID,   // apn_set_id
+                        -1,                     // carrier_id
+                        -1,                     // skip_464xlat
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
+                },
+                new Object[]{
+                        11,                      // id
+                        PLMN,                   // numeric
+                        IMS_APN,                // name
+                        IMS_APN,                // apn
+                        "",                     // proxy
+                        "",                     // port
+                        "",                     // mmsc
+                        "",                     // mmsproxy
+                        "",                     // mmsport
+                        "",                     // user
+                        "",                     // password
+                        -1,                     // authtype
+                        "ims",                  // types
+                        "IPV4V6",               // protocol
+                        "IPV4V6",               // roaming_protocol
+                        1,                      // carrier_enabled
+                        0,                      // profile_id
+                        1,                      // modem_cognitive
+                        0,                      // max_conns
+                        0,                      // wait_time
+                        0,                      // max_conns_time
+                        0,                      // mtu
+                        1280,                   // mtu_v4
+                        1280,                   // mtu_v6
+                        "",                     // mvno_type
+                        "",                     // mnvo_match_data
+                        TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                                | TelephonyManager.NETWORK_TYPE_BITMASK_NR, // network_type_bitmask
+                        0,                      // lingering_network_type_bitmask
+                        MATCH_ALL_APN_SET_ID,   // apn_set_id
+                        -1,                     // carrier_id
+                        -1,                     // skip_464xlat
+                        0,                      // always_on
+                        1,                      // INFRASTRUCTURE_SATELLITE
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
+                },
+                new Object[]{
+                        12,                     // id
+                        PLMN,                   // numeric
+                        TEST_BOOTSTRAP_APN,     // name
+                        TEST_BOOTSTRAP_APN,      // apn
+                        "",                     // proxy
+                        "",                     // port
+                        "",                     // mmsc
+                        "",                     // mmsproxy
+                        "",                     // mmsport
+                        "",                     // user
+                        "",                     // password
+                        -1,                     // authtype
+                        "default",              // types
+                        "IPV4V6",               // protocol
+                        "IPV4V6",               // roaming_protocol
+                        1,                      // carrier_enabled
+                        0,                      // profile_id
+                        1,                      // modem_cognitive
+                        0,                      // max_conns
+                        0,                      // wait_time
+                        0,                      // max_conns_time
+                        0,                      // mtu
+                        1280,                   // mtu_v4
+                        1280,                   // mtu_v6
+                        "",                     // mvno_type
+                        "",                     // mnvo_match_data
+                        TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                                | TelephonyManager.NETWORK_TYPE_BITMASK_NR, // network_type_bitmask
+                        0,                      // lingering_network_type_bitmask
+                        MATCH_ALL_APN_SET_ID,   // apn_set_id
+                        -1,                     // carrier_id
+                        -1,                     // skip_464xlat
+                        0,                      // always_on
+                        2,                      // INFRASTRUCTURE_SATELLITE
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
+                },
+                new Object[]{
+                        13,                     // id
+                        PLMN,                   // numeric
+                        RCS_APN1,               // name
+                        RCS_APN1,               // apn
+                        "",                     // proxy
+                        "",                     // port
+                        "",                     // mmsc
+                        "",                     // mmsproxy
+                        "",                     // mmsport
+                        "",                     // user
+                        "",                     // password
+                        -1,                     // authtype
+                        "rcs",                  // types
+                        "IPV4V6",               // protocol
+                        "IPV4V6",               // roaming_protocol
+                        1,                      // carrier_enabled
+                        0,                      // profile_id
+                        1,                      // modem_cognitive
+                        0,                      // max_conns
+                        0,                      // wait_time
+                        0,                      // max_conns_time
+                        0,                      // mtu
+                        1280,                   // mtu_v4
+                        1280,                   // mtu_v6
+                        "",                     // mvno_type
+                        "",                     // mnvo_match_data
+                        TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                                | TelephonyManager.NETWORK_TYPE_BITMASK_NR, // network_type_bitmask
+                        0,                      // lingering_network_type_bitmask
+                        DEFAULT_APN_SET_ID,     // apn_set_id
+                        -1,                     // carrier_id
+                        -1,                     // skip_464xlat
+                        0,                      // always_on
+                        2,                      // INFRASTRUCTURE_SATELLITE
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 }
         );
 
@@ -631,7 +858,8 @@
             return null;
         }).when(mDataProfileManagerCallback).invokeFromExecutor(any(Runnable.class));
         mDataProfileManagerUT = new DataProfileManager(mPhone, mDataNetworkController,
-                mMockedWwanDataServiceManager, Looper.myLooper(), mDataProfileManagerCallback);
+                mMockedWwanDataServiceManager, Looper.myLooper(), mFeatureFlags,
+                mDataProfileManagerCallback);
         ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
                 ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
         verify(mDataNetworkController).registerDataNetworkControllerCallback(
@@ -662,7 +890,7 @@
                 .build();
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_LTE, false);
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
         assertThat(dp.canSatisfy(tnr.getCapabilities())).isTrue();
         assertThat(dp.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
@@ -673,7 +901,7 @@
                 .build();
         tnr = new TelephonyNetworkRequest(request, mPhone);
         dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_LTE, false);
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
         assertThat(dp.canSatisfy(tnr.getCapabilities())).isTrue();
         assertThat(dp.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
@@ -683,7 +911,7 @@
                 .build();
         tnr = new TelephonyNetworkRequest(request, mPhone);
         dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_LTE, false);
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dp.canSatisfy(tnr.getCapabilities())).isTrue();
         assertThat(dp.getApnSetting().getApnName()).isEqualTo(IMS_APN);
 
@@ -692,7 +920,7 @@
                 .build();
         tnr = new TelephonyNetworkRequest(request, mPhone);
         dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_LTE, false);
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dp).isNull();
 
         doReturn(new NetworkRegistrationInfo.Builder()
@@ -705,7 +933,7 @@
                 .build();
         tnr = new TelephonyNetworkRequest(request, mPhone);
         dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_NR, false);
+                TelephonyManager.NETWORK_TYPE_NR, false, false , false);
         assertThat(dp.canSatisfy(tnr.getCapabilities())).isTrue();
         assertThat(dp.getApnSetting().getApnName()).isEqualTo(TETHERING_APN);
     }
@@ -717,7 +945,7 @@
                 .build();
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_GSM, false);
+                TelephonyManager.NETWORK_TYPE_GSM, false, false, false);
         // Should not find data profile due to RAT incompatible.
         assertThat(dp).isNull();
     }
@@ -729,14 +957,14 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .build(), mPhone);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
         logd("Set setLastSetupTimestamp on " + dataProfile);
         dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
 
         // See if another one can be returned.
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN1);
     }
 
@@ -747,7 +975,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                         .build(), mPhone);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting()).isNull();
         OsAppId osAppId = new OsAppId(dataProfile.getTrafficDescriptor().getOsAppId());
 
@@ -760,7 +988,7 @@
                 .addEnterpriseId(2), ConnectivityManager.TYPE_NONE,
                 0, NetworkRequest.Type.REQUEST), mPhone);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting()).isNull();
         osAppId = new OsAppId(dataProfile.getTrafficDescriptor().getOsAppId());
 
@@ -776,7 +1004,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
                         .build(), mPhone);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting()).isNull();
         OsAppId osAppId = new OsAppId(dataProfile.getTrafficDescriptor().getOsAppId());
 
@@ -792,7 +1020,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
                         .build(), mPhone);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting()).isNull();
         OsAppId osAppId = new OsAppId(dataProfile.getTrafficDescriptor().getOsAppId());
 
@@ -801,15 +1029,30 @@
         assertThat(osAppId.getDifferentiator()).isEqualTo(1);
     }
 
+    @Test
+    public void testGetDataProfileForSatellite() {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        NetworkRequest request = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
+                .build();
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
+                TelephonyManager.NETWORK_TYPE_LTE, true, false, false);
+
+        assertThat(dp.canSatisfy(tnr.getCapabilities())).isTrue();
+        assertThat(dp.getApnSetting().getApnName()).isEqualTo(RCS_APN);
+    }
 
     @Test
     public void testSetPreferredDataProfile() {
+        doReturn(true).when(mFeatureFlags).refinePreferredDataProfileSelection();
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .build(), mPhone);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
         dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
         dataProfile.setPreferred(true);
@@ -817,47 +1060,62 @@
         doReturn(dataProfile).when(internetNetwork).getDataProfile();
         doReturn(new DataNetworkController.NetworkRequestList(List.of(tnr)))
                 .when(internetNetwork).getAttachedNetworkRequestList();
-        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(internetNetwork));
+        mDataNetworkControllerCallback.onConnectedInternetDataNetworksChanged(
+                Set.of(internetNetwork));
         processAllMessages();
 
         // Test See if the same one can be returned.
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
 
         // Test Another default internet network connected due to RAT changed. Verify the preferred
         // data profile is updated.
         DataProfile legacyRatDataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_CDMA, false);
+                tnr, TelephonyManager.NETWORK_TYPE_CDMA, false, false, false);
         DataNetwork legacyRatInternetNetwork = Mockito.mock(DataNetwork.class);
         doReturn(legacyRatDataProfile).when(legacyRatInternetNetwork).getDataProfile();
         doReturn(new DataNetworkController.NetworkRequestList(List.of(tnr)))
                 .when(legacyRatInternetNetwork).getAttachedNetworkRequestList();
-        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(
+        mDataNetworkControllerCallback.onConnectedInternetDataNetworksChanged(Set.of(
                 // Because internetNetwork is torn down due to network type mismatch
                 legacyRatInternetNetwork));
         processAllMessages();
 
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(legacyRatDataProfile)).isTrue();
 
-        // Test Another supl default internet network temporarily connected. Verify preferred
-        // doesn't change.
-        TelephonyNetworkRequest suplTnr = new TelephonyNetworkRequest(
+        // Test Another dun default internet network temporarily connected. Verify preferred
+        // doesn't change. Mock DUN | DEFAULT.
+        TelephonyNetworkRequest dunTnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
-                        .addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
                         .build(), mPhone);
-        DataProfile suplDataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                suplTnr, TelephonyManager.NETWORK_TYPE_LTE, false);
-        DataNetwork suplInternetNetwork = Mockito.mock(DataNetwork.class);
-        doReturn(suplDataProfile).when(suplInternetNetwork).getDataProfile();
-        doReturn(new DataNetworkController.NetworkRequestList(List.of(suplTnr)))
-                .when(suplInternetNetwork).getAttachedNetworkRequestList();
-        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(
-                legacyRatInternetNetwork, suplInternetNetwork));
+        DataProfile dunDataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                dunTnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
+        DataNetwork dunInternetNetwork = Mockito.mock(DataNetwork.class);
+        doReturn(dunDataProfile).when(dunInternetNetwork).getDataProfile();
+        doReturn(new DataNetworkController.NetworkRequestList(List.of(dunTnr)))
+                .when(dunInternetNetwork).getAttachedNetworkRequestList();
+        mDataNetworkControllerCallback.onConnectedInternetDataNetworksChanged(Set.of(
+                legacyRatInternetNetwork, dunInternetNetwork));
         processAllMessages();
 
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(legacyRatDataProfile)).isTrue();
+
+        // Test a single dun default internet network temporarily connected. Verify preferred
+        // doesn't change. Mock DUN | DEFAULT and enforced single connection.
+        mDataNetworkControllerCallback.onConnectedInternetDataNetworksChanged(
+                Set.of(dunInternetNetwork));
+        processAllMessages();
+
+        assertThat(mDataProfileManagerUT.isDataProfilePreferred(legacyRatDataProfile)).isTrue();
+
+        // Test all internet networks disconnected. Verify preferred doesn't change.
+        mDataNetworkControllerCallback.onConnectedInternetDataNetworksChanged(
+                Collections.emptySet());
+
+        assertThat(mDataProfileManagerUT.isDataProfilePreferred(legacyRatDataProfile)).isTrue();
     }
 
     @Test
@@ -922,7 +1180,7 @@
 
         // SIM removed
         Mockito.clearInvocations(mDataProfileManagerCallback);
-        mSimInserted = false;
+        changeSimStateTo(TelephonyManager.SIM_STATE_ABSENT);
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
         processAllMessages();
 
@@ -933,7 +1191,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .build(), mPhone);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile).isNull();
 
         // expect default EIMS when SIM absent
@@ -942,7 +1200,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
                         .build(), mPhone);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo("sos");
 
         // expect no default IMS when SIM absent
@@ -951,7 +1209,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                         .build(), mPhone);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile).isEqualTo(null);
 
         // Verify null as initial attached data profile is sent to modem
@@ -981,7 +1239,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .build(), mPhone);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile).isNull();
 
         // expect default EIMS when SIM absent
@@ -990,7 +1248,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
                         .build(), mPhone);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo("sos");
 
         // expect no default IMS when SIM absent
@@ -999,7 +1257,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                         .build(), mPhone);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile).isEqualTo(null);
 
         // Verify in legacy mode, null IA should NOT be sent to modem
@@ -1016,7 +1274,7 @@
         doReturn(List.of(ApnSetting.TYPE_IMS))
                 .when(mDataConfigManager).getAllowedInitialAttachApnTypes();
 
-        mSimInserted = true;
+        changeSimStateTo(TelephonyManager.SIM_STATE_LOADED);
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
         processAllMessages();
 
@@ -1034,7 +1292,7 @@
                 new TelephonyNetworkRequest(new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                         .build(), mPhone),
-                TelephonyManager.NETWORK_TYPE_LTE, false);
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(IMS_APN);
     }
 
@@ -1046,7 +1304,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
         // This should get the merged data profile after deduping.
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_LTE, false);
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dp.canSatisfy(NetworkCapabilities.NET_CAPABILITY_INTERNET)).isTrue();
     }
 
@@ -1055,6 +1313,7 @@
         DataProfile dataProfile1 = new DataProfile.Builder()
                 .setApnSetting(new ApnSetting.Builder()
                         .setEntryName("general")
+                        .setOperatorNumeric("123456")
                         .setApnName("apn")
                         .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS
                                 | ApnSetting.TYPE_SUPL | ApnSetting.TYPE_HIPRI)
@@ -1075,6 +1334,7 @@
         DataProfile dataProfile2 = new DataProfile.Builder()
                 .setApnSetting(new ApnSetting.Builder()
                         .setEntryName("XCAP")
+                        .setOperatorNumeric("123456")
                         .setApnName("apn")
                         .setApnTypeBitmask(ApnSetting.TYPE_XCAP)
                         .setUser("user")
@@ -1181,7 +1441,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
                 .build(), mPhone);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
         assertThat(dataProfile.getApn()).isEqualTo("sos");
         assertThat(dataProfile.getTrafficDescriptor().getDataNetworkName()).isEqualTo("sos");
@@ -1189,7 +1449,7 @@
 
     @Test
     public void testResetApn() {
-        mSimInserted = true;
+        changeSimStateTo(TelephonyManager.SIM_STATE_LOADED);
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
         processAllMessages();
 
@@ -1198,12 +1458,16 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .build(), mPhone);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
         dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
         DataNetwork internetNetwork = Mockito.mock(DataNetwork.class);
         doReturn(dataProfile).when(internetNetwork).getDataProfile();
-        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(internetNetwork));
+
+        doReturn(new DataNetworkController.NetworkRequestList(List.of(tnr)))
+                .when(internetNetwork).getAttachedNetworkRequestList();
+        mDataNetworkControllerCallback.onConnectedInternetDataNetworksChanged(
+                Set.of(internetNetwork));
         processAllMessages();
 
         // After internet connected, preferred APN should be set
@@ -1235,7 +1499,8 @@
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
 
         // Test removed data profile(user created after reset) shouldn't show up
-        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(internetNetwork));
+        mDataNetworkControllerCallback.onConnectedInternetDataNetworksChanged(
+                Set.of(internetNetwork));
         processAllMessages();
         //APN reset and removed GENERAL_PURPOSE_APN from APN DB
         mPreferredApnId = -1;
@@ -1258,13 +1523,13 @@
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .build(), mPhone);
-        mSimInserted = true;
+        changeSimStateTo(TelephonyManager.SIM_STATE_LOADED);
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
         processAllMessages();
 
         // The carrier configured data profile should be the preferred APN after APN reset
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN1);
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
@@ -1277,7 +1542,7 @@
 
         // The carrier configured data profile should be the preferred APN after APN reset
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN1);
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
     }
@@ -1342,6 +1607,7 @@
                 .setApnSetting(new ApnSetting.Builder()
                         .setEntryName(GENERAL_PURPOSE_APN)
                         .setId(1)
+                        .setOperatorNumeric(PLMN)
                         .setApnName(GENERAL_PURPOSE_APN)
                         .setProxyAddress("")
                         .setMmsProxyAddress("")
@@ -1360,6 +1626,7 @@
                                 | TelephonyManager.NETWORK_TYPE_BITMASK_NR))
                         .setMvnoMatchData("")
                         .setApnSetId(DEFAULT_APN_SET_ID)
+                        .setInfrastructureBitmask(1)
                         .build())
                 .build();
 
@@ -1369,7 +1636,7 @@
     @Test
     public void testDataProfileCompatibility_FilteringWithPreferredApnSetIdAsDefault() {
         mApnSettingContentProvider.setPreferredApn(GENERAL_PURPOSE_APN);
-        mSimInserted = true;
+        changeSimStateTo(TelephonyManager.SIM_STATE_LOADED);
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
         processAllMessages();
 
@@ -1395,6 +1662,7 @@
                         .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_NR))
                         .setMvnoMatchData("")
                         .setApnSetId(DEFAULT_APN_SET_ID)
+                        .setInfrastructureBitmask(1)
                         .build())
                 .build();
 
@@ -1423,6 +1691,7 @@
                                 | TelephonyManager.NETWORK_TYPE_BITMASK_NR))
                         .setMvnoMatchData("")
                         .setApnSetId(APN_SET_ID_1)
+                        .setInfrastructureBitmask(1)
                         .build())
                 .build();
 
@@ -1451,6 +1720,7 @@
                                 | TelephonyManager.NETWORK_TYPE_BITMASK_NR))
                         .setMvnoMatchData("")
                         .setApnSetId(MATCH_ALL_APN_SET_ID)
+                        .setInfrastructureBitmask(1)
                         .build())
                 .build();
 
@@ -1460,7 +1730,7 @@
     @Test
     public void testDataProfileCompatibility_FilteringWithPreferredApnSetIdAs1() {
         mApnSettingContentProvider.setPreferredApn(APN_SET_ID_1_APN);
-        mSimInserted = true;
+        changeSimStateTo(TelephonyManager.SIM_STATE_LOADED);
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
         processAllMessages();
 
@@ -1486,6 +1756,7 @@
                                 | TelephonyManager.NETWORK_TYPE_BITMASK_NR))
                         .setMvnoMatchData("")
                         .setApnSetId(APN_SET_ID_1)
+                        .setInfrastructureBitmask(1)
                         .build())
                 .build();
 
@@ -1514,6 +1785,7 @@
                         .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_NR))
                         .setMvnoMatchData("")
                         .setApnSetId(DEFAULT_APN_SET_ID)
+                        .setInfrastructureBitmask(1)
                         .build())
                 .build();
 
@@ -1542,6 +1814,7 @@
                                 | TelephonyManager.NETWORK_TYPE_BITMASK_NR))
                         .setMvnoMatchData("")
                         .setApnSetId(MATCH_ALL_APN_SET_ID)
+                        .setInfrastructureBitmask(1)
                         .build())
                 .build();
 
@@ -1557,7 +1830,7 @@
                 .build();
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_LTE, false);
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
         // Mark the APN as permanent failed.
         dp.getApnSetting().setPermanentFailed(true);
@@ -1565,7 +1838,8 @@
         // Data profile manager should return a different data profile for setup as the previous
         // data profile has been marked as permanent failed.
         assertThat(mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_LTE, false)).isNotEqualTo(dp);
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false))
+                .isNotEqualTo(dp);
     }
 
     @Test
@@ -1580,7 +1854,7 @@
                 .build();
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_LTE, false);
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
         // Mark the APN as permanent failed.
         dp.getApnSetting().setPermanentFailed(true);
@@ -1588,6 +1862,120 @@
         // Since preferred APN is already set, and that data profile was marked as permanent failed,
         // so this should result in getting nothing.
         assertThat(mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
-                TelephonyManager.NETWORK_TYPE_LTE, false)).isNull();
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false))
+                .isNull();
     }
+
+    private void changeSimStateTo(@TelephonyManager.SimState int simState) {
+        mSimInserted = simState == TelephonyManager.SIM_STATE_LOADED;
+        mDataNetworkControllerCallback.onSimStateChanged(simState);
+    }
+
+    @Test
+    public void testClearAllDataProfilePermanentFailures() {
+        testPermanentFailureWithPreferredDataProfile();
+
+        // Reset all data profiles
+        mDataProfileManagerUT.clearAllDataProfilePermanentFailures();
+
+        NetworkRequest request = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
+
+        // Verify the we can get the previously permanent failed data profile again.
+        assertThat(mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                new TelephonyNetworkRequest(request, mPhone),
+                TelephonyManager.NETWORK_TYPE_LTE, false, false, false))
+                .isNotNull();
+    }
+
+    @Test
+    public void testDifferentNetworkRequestProfilesOnEsimBootStrapProvisioning() {
+        Mockito.clearInvocations(mDataProfileManagerCallback);
+        Mockito.clearInvocations(mMockedWwanDataServiceManager);
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        // SIM inserted
+        mDataProfileManagerUT.obtainMessage(3 /* EVENT_SIM_REFRESH */).sendToTarget();
+        processAllMessages();
+
+        // expect default profile for internet network request, when esim bootstrap provisioning
+        // flag is enabled at data profile, during esim bootstrap provisioning
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build(), mPhone);
+        DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
+        assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(
+                "ESIM_BOOTSTRAP_PROVISIONING_APN");
+
+        // expect IMS profile for ims network request, when esim bootstrap provisioning flag
+        // is enabled at data profile, during esim bootstrap provisioning
+        tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                 .build(), mPhone);
+        dataProfile  = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
+        assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo("IMS_APN");
+
+        // expect no mms profile for mms network request, when esim bootstrap provisioning flag
+        // is disabled at data profile, during esim bootstrap provisioning
+        tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
+                .build(), mPhone);
+        dataProfile  = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
+        assertThat(dataProfile).isEqualTo(null);
+
+        // expect no rcs profile for rcs network request, when esim bootstrap provisioning flag
+        // is disabled at data profile, during esim bootstrap provisioning
+        tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
+                .build(), mPhone);
+        dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
+        assertThat(dataProfile).isEqualTo(null);
+    }
+
+    @Test
+    public void testEsimBootstrapProvisioningEnabled_MultipleProfile() {
+        Mockito.clearInvocations(mDataProfileManagerCallback);
+        Mockito.clearInvocations(mMockedWwanDataServiceManager);
+
+        // SIM inserted
+        mDataProfileManagerUT.obtainMessage(3 /* EVENT_SIM_REFRESH */).sendToTarget();
+        processAllMessages();
+
+        // expect initial default profile entry selected for internet network request, when
+        // multiple esim bootstrap provisioning flag is enabled at data profile for same apn
+        // type
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build(), mPhone);
+        DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
+        assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(
+                ESIM_BOOTSTRAP_PROVISIONING_APN);
+    }
+
+    @Test
+    public void testInfrastructureProfileOnEsimBootStrapProvisioning() {
+        Mockito.clearInvocations(mDataProfileManagerCallback);
+        Mockito.clearInvocations(mMockedWwanDataServiceManager);
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        // SIM inserted
+        mDataProfileManagerUT.obtainMessage(3 /* EVENT_SIM_REFRESH */).sendToTarget();
+        processAllMessages();
+
+        // expect initial default profile entry selected for internet network request, when
+        // multiple esim bootstrap provisioning flag is enabled at data profile for same apn
+        // type
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
+                .build(), mPhone);
+        DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, true, true, false);
+        assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(RCS_APN1);
+    }
+
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileTest.java
index 4a2eb38..f5981f7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileTest.java
@@ -179,6 +179,7 @@
         assertThat(dp.canSatisfy(NetworkCapabilities.NET_CAPABILITY_MMS)).isFalse();
         assertThat(dp.canSatisfy(new int[]{NetworkCapabilities.NET_CAPABILITY_INTERNET,
                 NetworkCapabilities.NET_CAPABILITY_MMS})).isFalse();
+        assertThat(dp.canSatisfy(new int[]{NetworkCapabilities.NET_CAPABILITY_RCS})).isFalse();
     }
 
     @Test
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 022323a..2541bd1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
@@ -33,8 +33,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.Intent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.AsyncResult;
@@ -160,7 +158,7 @@
         mockedDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mMockedWlanDataServiceManager);
         mDataRetryManagerUT = new DataRetryManager(mPhone, mDataNetworkController,
-                mockedDataServiceManagers, Looper.myLooper(),
+                mockedDataServiceManagers, Looper.myLooper(), mFeatureFlags,
                 mDataRetryManagerCallbackMock);
 
         ArgumentCaptor<DataConfigManagerCallback> dataConfigManagerCallbackCaptor =
@@ -343,6 +341,7 @@
 
     @Test
     public void testDataSetupUnthrottling() throws Exception {
+        doReturn(true).when(mFeatureFlags).unthrottleCheckTransport();
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                 .build();
@@ -787,6 +786,7 @@
 
     @Test
     public void testDataRetryLongTimer() {
+        doReturn(true).when(mFeatureFlags).useAlarmCallback();
         // Rule requires a long timer
         DataSetupRetryRule retryRule = new DataSetupRetryRule(
                 "capabilities=internet, retry_interval=120000, maximum_retries=2");
@@ -807,16 +807,15 @@
         processAllFutureMessages();
 
         // Verify scheduled via Alarm Manager
-        ArgumentCaptor<PendingIntent> pendingIntentArgumentCaptor =
-                ArgumentCaptor.forClass(PendingIntent.class);
-        verify(mAlarmManager).setExactAndAllowWhileIdle(anyInt(), anyLong(),
-                pendingIntentArgumentCaptor.capture());
+        ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        verify(mAlarmManager).setExactAndAllowWhileIdle(anyInt(), anyLong(), any(), any(), any(),
+                alarmListenerCaptor.capture());
 
-        // Verify starts retry attempt after receiving intent
-        PendingIntent pendingIntent = pendingIntentArgumentCaptor.getValue();
-        Intent intent = pendingIntent.getIntent();
-        mContext.sendBroadcast(intent);
-        processAllFutureMessages();
+        // Verify starts retry attempt after alarm fires.
+        AlarmManager.OnAlarmListener alarmListener = alarmListenerCaptor.getValue();
+        alarmListener.onAlarm();
+        processAllMessages();
 
         verify(mDataRetryManagerCallbackMock)
                 .onDataNetworkSetupRetry(any(DataSetupRetryEntry.class));
@@ -864,7 +863,7 @@
     @Test
     public void testRilCrashedReset() {
         testDataSetupRetryNetworkSuggestedNeverRetry();
-        Mockito.clearInvocations(mDataRetryManagerCallbackMock);
+        Mockito.clearInvocations(mDataRetryManagerCallbackMock, mDataProfileManager);
 
         // RIL crashed and came back online.
         mDataRetryManagerUT.obtainMessage(8/*EVENT_RADIO_ON*/,
@@ -884,12 +883,13 @@
         assertThat(throttleStatus.getThrottleExpiryTimeMillis()).isEqualTo(-1);
         assertThat(throttleStatus.getTransportType())
                 .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        verify(mDataProfileManager).clearAllDataProfilePermanentFailures();
     }
 
     @Test
     public void testModemCrashedReset() {
         testDataSetupRetryNetworkSuggestedNeverRetry();
-        Mockito.clearInvocations(mDataRetryManagerCallbackMock);
+        Mockito.clearInvocations(mDataRetryManagerCallbackMock, mDataProfileManager);
 
         // RIL crashed and came back online.
         mDataRetryManagerUT.obtainMessage(10 /*EVENT_TAC_CHANGED*/,
@@ -909,6 +909,7 @@
         assertThat(throttleStatus.getThrottleExpiryTimeMillis()).isEqualTo(-1);
         assertThat(throttleStatus.getTransportType())
                 .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        verify(mDataProfileManager).clearAllDataProfilePermanentFailures();
     }
 
     @Test
@@ -916,7 +917,7 @@
         doReturn(true).when(mDataConfigManager).shouldResetDataThrottlingWhenTacChanges();
 
         testDataSetupRetryNetworkSuggestedNeverRetry();
-        Mockito.clearInvocations(mDataRetryManagerCallbackMock);
+        Mockito.clearInvocations(mDataRetryManagerCallbackMock, mDataProfileManager);
 
         // RIL crashed and came back online.
         mDataRetryManagerUT.obtainMessage(9/*EVENT_MODEM_RESET*/,
@@ -936,5 +937,6 @@
         assertThat(throttleStatus.getThrottleExpiryTimeMillis()).isEqualTo(-1);
         assertThat(throttleStatus.getTransportType())
                 .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        verify(mDataProfileManager).clearAllDataProfilePermanentFailures();
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataServiceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataServiceManagerTest.java
index 88f642b..30f49ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataServiceManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataServiceManagerTest.java
@@ -155,7 +155,7 @@
                 message);
         waitAndVerifyResult(message, DataServiceCallback.RESULT_SUCCESS);
         verify(mSimulatedCommandsVerifier).setupDataCall(anyInt(), any(DataProfile.class),
-                anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(), any(), any(), anyBoolean(),
+                anyBoolean(), anyInt(), any(), anyInt(), any(), any(), anyBoolean(),
                 any(Message.class));
     }
 
@@ -168,7 +168,7 @@
                 message);
         waitAndVerifyResult(message, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
         verify(mSimulatedCommandsVerifier, never()).setupDataCall(anyInt(), any(DataProfile.class),
-                anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(), any(), any(), anyBoolean(),
+                anyBoolean(), anyInt(), any(), anyInt(), any(), any(), anyBoolean(),
                 any(Message.class));
     }
 
@@ -199,7 +199,7 @@
         mDataServiceManagerUT.setInitialAttachApn(mGeneralPurposeDataProfile, false, message);
         waitAndVerifyResult(message, DataServiceCallback.RESULT_SUCCESS);
         verify(mSimulatedCommandsVerifier).setInitialAttachApn(any(DataProfile.class),
-                anyBoolean(), any(Message.class));
+                any(Message.class));
     }
 
     @Test
@@ -209,7 +209,7 @@
         mDataServiceManagerUT.setInitialAttachApn(mGeneralPurposeDataProfile, false, message);
         waitAndVerifyResult(message, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
         verify(mSimulatedCommandsVerifier, never()).setInitialAttachApn(any(DataProfile.class),
-                anyBoolean(), any(Message.class));
+                any(Message.class));
     }
 
     @Test
@@ -218,7 +218,7 @@
         Message message = mHandler.obtainMessage(1234);
         mDataServiceManagerUT.setDataProfile(List.of(mGeneralPurposeDataProfile), false, message);
         waitAndVerifyResult(message, DataServiceCallback.RESULT_SUCCESS);
-        verify(mSimulatedCommandsVerifier).setDataProfile(any(DataProfile[].class), anyBoolean(),
+        verify(mSimulatedCommandsVerifier).setDataProfile(any(DataProfile[].class),
                 any(Message.class));
     }
 
@@ -229,7 +229,7 @@
         mDataServiceManagerUT.setDataProfile(List.of(mGeneralPurposeDataProfile), false, message);
         waitAndVerifyResult(message, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
         verify(mSimulatedCommandsVerifier, never()).setDataProfile(any(DataProfile[].class),
-                anyBoolean(), any(Message.class));
+                any(Message.class));
     }
 
     @Test
@@ -267,4 +267,20 @@
         waitAndVerifyResult(message, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
         verify(mSimulatedCommandsVerifier, never()).cancelHandover(any(Message.class), anyInt());
     }
+
+    @Test
+    public void testRequestNetworkValidation_ServiceNotBound() throws Exception {
+        createDataServiceManager(false);
+        Message message = mHandler.obtainMessage(1234);
+        mDataServiceManagerUT.requestNetworkValidation(123, message);
+        waitAndVerifyResult(message, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+    }
+
+    @Test
+    public void testRequestNetworkValidation_ServiceBound() throws Exception {
+        createDataServiceManager(true);
+        Message message = mHandler.obtainMessage(1234);
+        mDataServiceManagerUT.requestNetworkValidation(123, message);
+        waitAndVerifyResult(message, DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
index 20cb73a..fc1bf0d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
@@ -29,7 +29,9 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.NonNull;
 import android.os.Looper;
+import android.os.Message;
 import android.os.PersistableBundle;
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
@@ -49,6 +51,8 @@
 import org.mockito.Mockito;
 
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -178,4 +182,26 @@
         callback.onUserDataEnabledChanged(true, "callingPackage");
         verify(mPhone).notifyDataEnabled(true, TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
     }
+
+    @Test
+    public void testNotifyDataEnabledFromNewValidSubId() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mDataSettingsManagerUT.registerCallback(
+                new DataSettingsManagerCallback(mDataSettingsManagerUT::post) {
+                    @Override
+                    public void onDataEnabledChanged(boolean enabled,
+                            @TelephonyManager.DataEnabledChangedReason int reason,
+                            @NonNull String callingPackage) {
+                        latch.countDown();
+                    }
+                });
+
+        Message.obtain(mDataSettingsManagerUT, 4 /* EVENT_SUBSCRIPTIONS_CHANGED */, -1)
+                .sendToTarget();
+        Message.obtain(mDataSettingsManagerUT, 4 /* EVENT_SUBSCRIPTIONS_CHANGED */, 2)
+                .sendToTarget();
+        processAllMessages();
+
+        assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
index 22cdaae..e508e5b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.database.ContentObserver;
 import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
@@ -42,6 +43,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
+import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.DataStallRecoveryManager.DataStallRecoveryManagerCallback;
 
 import org.junit.After;
@@ -51,8 +53,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Set;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -107,7 +108,7 @@
         doReturn(dataStallRecoveryStepsArray)
                 .when(mDataConfigManager)
                 .getDataStallRecoveryShouldSkipArray();
-        doReturn(true).when(mDataNetworkController).isInternetDataAllowed();
+        doReturn(true).when(mDataNetworkController).isInternetDataAllowed(true);
 
         doAnswer(invocation -> {
             ((Runnable) invocation.getArguments()[0]).run();
@@ -145,6 +146,17 @@
         dataNetworkControllerCallback.onInternetDataNetworkValidationStatusChanged(status);
     }
 
+    private void sendDataEabledCallback(boolean isEnabled) {
+        ArgumentCaptor<DataSettingsManagerCallback> dataSettingsManagerCallbackCaptor =
+                ArgumentCaptor.forClass(DataSettingsManagerCallback.class);
+        verify(mDataSettingsManager).registerCallback(dataSettingsManagerCallbackCaptor.capture());
+
+        // Data enabled
+        doReturn(isEnabled).when(mDataSettingsManager).isDataEnabled();
+        dataSettingsManagerCallbackCaptor.getValue().onDataEnabledChanged(isEnabled,
+                TelephonyManager.DATA_ENABLED_REASON_USER, "");
+    }
+
     private void sendOnInternetDataNetworkCallback(boolean isConnected) {
         ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
                 ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
@@ -154,12 +166,14 @@
         DataNetworkControllerCallback dataNetworkControllerCallback =
                 dataNetworkControllerCallbackCaptor.getAllValues().get(0);
 
-        if (isConnected) {
-            List<DataNetwork> dataprofile = new ArrayList<>();
-            dataNetworkControllerCallback.onInternetDataNetworkConnected(dataprofile);
-        } else {
-            dataNetworkControllerCallback.onInternetDataNetworkDisconnected();
+        DataNetwork network = mock(DataNetwork.class);
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        doReturn(netCaps).when(network).getNetworkCapabilities();
+        if (!isConnected) {
+            // A network that doesn't need to be tracked for validation
+            netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
         }
+        dataNetworkControllerCallback.onConnectedInternetDataNetworksChanged(Set.of(network));
         processAllMessages();
     }
 
@@ -255,7 +269,8 @@
         moveTimeForward(15000);
         processAllMessages();
 
-        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(3);
+        // should not change the recovery action due to there is an active call.
+        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(1);
     }
 
     @Test
@@ -345,7 +360,7 @@
         mDataStallRecoveryManager.setRecoveryAction(1);
         doReturn(mSignalStrength).when(mPhone).getSignalStrength();
         doReturn(PhoneConstants.State.IDLE).when(mPhone).getState();
-        doReturn(false).when(mDataNetworkController).isInternetDataAllowed();
+        doReturn(false).when(mDataNetworkController).isInternetDataAllowed(true);
 
         logd("Sending validation failed callback");
         sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
@@ -508,4 +523,59 @@
         // Check if predict waiting millis is 0
         assertThat(field.get(mDataStallRecoveryManager)).isEqualTo(0L);
     }
+
+    @Test
+    public void testRecoveryActionAfterDataEnabled() throws Exception {
+        sendDataEabledCallback(true);
+        sendOnInternetDataNetworkCallback(true);
+        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_VALID);
+        mDataStallRecoveryManager.setRecoveryAction(0);
+        doReturn(PhoneConstants.State.IDLE).when(mPhone).getState();
+        doReturn(3).when(mSignalStrength).getLevel();
+        doReturn(mSignalStrength).when(mPhone).getSignalStrength();
+        logd("Sending validation failed callback");
+
+        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(0);
+        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        processAllMessages();
+        moveTimeForward(101);
+        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(1);
+
+        // test mobile data off/on
+        sendDataEabledCallback(false);
+        sendDataEabledCallback(true);
+
+        // recovery action will jump to next action if user doing the mobile data off/on.
+        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(3);
+    }
+
+    @Test
+    public void testJumpToRecoveryActionRadioRestart() throws Exception {
+        sendDataEabledCallback(true);
+        sendOnInternetDataNetworkCallback(true);
+        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_VALID);
+        mDataStallRecoveryManager.setRecoveryAction(0);
+
+        doReturn(PhoneConstants.State.IDLE).when(mPhone).getState();
+        doReturn(3).when(mSignalStrength).getLevel();
+        doReturn(mSignalStrength).when(mPhone).getSignalStrength();
+        doReturn(TelephonyManager.RADIO_POWER_ON).when(mPhone).getRadioPowerState();
+        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(0);
+
+        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        moveTimeForward(200);
+        processAllMessages();
+        moveTimeForward(200);
+        mDataStallRecoveryManager.sendMessageDelayed(
+                mDataStallRecoveryManager.obtainMessage(3), 1000);
+        processAllMessages();
+        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        moveTimeForward(200);
+        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        processAllMessages();
+        moveTimeForward(200);
+
+        // recovery action will jump to modem reset action if user doing the radio restart.
+        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(4);
+    }
 }
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 807f44c..e011a60 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
@@ -26,8 +26,10 @@
 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 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;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -66,10 +68,11 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
@@ -267,12 +270,14 @@
         assertFalse("data allowed", mDataAllowed[0]);
 
         setSlotIndexToSubId(1, 1);
+        clearInvocations(mAutoDataSwitchController);
         mSubChangedListener.onSubscriptionsChanged();
         processAllMessages();
 
         Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, res).sendToTarget();
         processAllMessages();
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        verify(mAutoDataSwitchController).notifySubscriptionsMappingChanged();
         clearInvocations(mActivePhoneSwitchHandler);
         assertFalse("data allowed", mDataAllowed[0]);
         assertTrue("data not allowed", mDataAllowed[1]);
@@ -289,8 +294,10 @@
 
         // 3 lose default via sub->phone change
         setSlotIndexToSubId(0, 2);
+        clearInvocations(mAutoDataSwitchController);
         mSubChangedListener.onSubscriptionsChanged();
         processAllMessages();
+        verify(mAutoDataSwitchController).notifySubscriptionsMappingChanged();
         Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, res).sendToTarget();
         processAllMessages();
 
@@ -632,7 +639,7 @@
         // A higher priority event occurring E.g. Phone1 has active IMS call on LTE.
         doReturn(mImsPhone).when(mPhone).getImsPhone();
         doReturn(true).when(mPhone).isUserDataEnabled();
-        doReturn(true).when(mPhone).isDataAllowed();
+        doReturn(true).when(mDataSettingsManager).isDataEnabled();
         mockImsRegTech(0, REGISTRATION_TECH_LTE);
         notifyPhoneAsInCall(mPhone);
 
@@ -679,6 +686,37 @@
     }
 
     @Test
+    public void testSetAutoSelectedValidationFeatureNotSupported() throws Exception {
+        doReturn(false).when(mCellularNetworkValidator).isValidationFeatureSupported();
+        initialize();
+
+        // 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);
+
+        doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService
+                .getSubscriptionInfoInternal(2)).setOpportunistic(1).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2);
+
+        mPhoneSwitcherUT.trySetOpportunisticDataSubscription(2, false, mSetOpptDataCallback1);
+        processAllMessages();
+        mPhoneSwitcherUT.mValidationCallback.onNetworkAvailable(null, 2);
+        processAllMessages();
+        assertEquals(2, mPhoneSwitcherUT.getAutoSelectedDataSubId());
+
+        // Switch to the default sub, verify AutoSelectedDataSubId is the default value.
+        clearInvocations(mSetOpptDataCallback1);
+        mPhoneSwitcherUT.trySetOpportunisticDataSubscription(SubscriptionManager
+                .DEFAULT_SUBSCRIPTION_ID, true, mSetOpptDataCallback1);
+        processAllMessages();
+        assertEquals(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                mPhoneSwitcherUT.getAutoSelectedDataSubId());
+    }
+
+    @Test
     @SmallTest
     public void testSetPreferredDataModemCommand() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -853,7 +891,7 @@
         // Phone2 has active IMS call on LTE. And data of DEFAULT apn is enabled. This should
         // trigger data switch.
         doReturn(mImsPhone).when(mPhone2).getImsPhone();
-        doReturn(true).when(mPhone2).isDataAllowed();
+        doReturn(true).when(mDataSettingsManager2).isDataEnabled();
         mockImsRegTech(1, REGISTRATION_TECH_LTE);
         notifyPhoneAsInCall(mImsPhone);
 
@@ -882,7 +920,7 @@
         // Dialing shouldn't trigger switch because we give modem time to deal with the dialing call
         // first. Phone2 has active IMS call on LTE. And data of DEFAULT apn is enabled.
         doReturn(mImsPhone).when(mPhone2).getImsPhone();
-        doReturn(true).when(mPhone2).isDataAllowed();
+        doReturn(true).when(mDataSettingsManager2).isDataEnabled();
         mockImsRegTech(1, REGISTRATION_TECH_LTE);
         notifyPhoneAsInDial(mImsPhone);
 
@@ -916,7 +954,7 @@
         // Phone2 has active IMS call on LTE. And data of DEFAULT apn is enabled. This should
         // trigger data switch.
         doReturn(mImsPhone).when(mPhone2).getImsPhone();
-        doReturn(true).when(mPhone2).isDataAllowed();
+        doReturn(true).when(mDataSettingsManager2).isDataEnabled();
         mockImsRegTech(1, REGISTRATION_TECH_LTE);
         notifyPhoneAsInIncomingCall(mImsPhone);
 
@@ -943,7 +981,7 @@
 
         // Phone2 has active call, but data is turned off. So no data switching should happen.
         doReturn(mImsPhone).when(mPhone2).getImsPhone();
-        doReturn(true).when(mPhone2).isDataAllowed();
+        doReturn(true).when(mDataSettingsManager2).isDataEnabled();
         mockImsRegTech(1, REGISTRATION_TECH_IWLAN);
         notifyPhoneAsInCall(mImsPhone);
 
@@ -972,7 +1010,7 @@
         // not trigger data switch.
         doReturn(mImsPhone).when(mPhone2).getImsPhone();
         doReturn(true).when(mPhone).isUserDataEnabled();
-        doReturn(true).when(mPhone2).isDataAllowed();
+        doReturn(true).when(mDataSettingsManager2).isDataEnabled();
         mockImsRegTech(1, REGISTRATION_TECH_CROSS_SIM);
         notifyPhoneAsInCall(mImsPhone);
 
@@ -1066,7 +1104,6 @@
     }
 
     @Test
-    @SmallTest
     public void testDataEnabledChangedDuringVoiceCall() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1104,6 +1141,18 @@
     }
 
     @Test
+    public void testRoamingToggle() throws Exception {
+        initialize();
+        setSlotIndexToSubId(0, 1);
+
+        mDataSettingsManagerCallbacks.get(0).onDataRoamingEnabledChanged(true);
+        processAllMessages();
+
+        verify(mAutoDataSwitchController).evaluateAutoDataSwitch(AutoDataSwitchController
+                .EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+    }
+
+    @Test
     @SmallTest
     public void testNetworkRequestOnNonDefaultData() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1357,14 +1406,23 @@
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2);
 
         // Switch to primary before a primary is selected/inactive.
-        setDefaultDataSubId(-1);
+        setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        mPhoneSwitcherUT.trySetOpportunisticDataSubscription(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID, false, mSetOpptDataCallback1);
+        processAllMessages();
+
+        assertEquals(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                mPhoneSwitcherUT.getAutoSelectedDataSubId());
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
+
+        // Verify that the switch to default sub is successful
         mPhoneSwitcherUT.trySetOpportunisticDataSubscription(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, mSetOpptDataCallback1);
         processAllMessages();
 
         assertEquals(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
                 mPhoneSwitcherUT.getAutoSelectedDataSubId());
-        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
 
         // once the primary is selected, it becomes the active sub.
         setDefaultDataSubId(2);
@@ -1664,6 +1722,7 @@
 
         // 2.2 Auto switch feature is enabled
         doReturn(true).when(mPhone2).isDataAllowed();
+        doReturn(true).when(mDataSettingsManager2).isDataEnabled();
 
         // 3.1 No default network
         doReturn(null).when(mConnectivityManager).getNetworkCapabilities(any());
@@ -1743,7 +1802,7 @@
     private void notifyDataEnabled(boolean dataEnabled) {
         doReturn(true).when(mPhone).isUserDataEnabled();
         doReturn(dataEnabled).when(mDataSettingsManager).isDataEnabled();
-        doReturn(dataEnabled).when(mPhone2).isDataAllowed();
+        doReturn(dataEnabled).when(mDataSettingsManager2).isDataEnabled();
         mDataSettingsManagerCallbacks.get(0).onDataEnabledChanged(dataEnabled, 123 , "");
         if (mDataSettingsManagerCallbacks.size() > 1) {
             mDataSettingsManagerCallbacks.get(1).onDataEnabledChanged(dataEnabled, 123, "");
@@ -1826,7 +1885,8 @@
         initializeConnManagerMock();
         initializeConfigMock();
 
-        mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper());
+        mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper(),
+                mFeatureFlags);
 
         Field field = PhoneSwitcher.class.getDeclaredField("mDataSettingsManagerCallbacks");
         field.setAccessible(true);
@@ -1876,6 +1936,17 @@
         }
 
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        for (Phone phone : mPhones) {
+            ServiceState ss = new ServiceState();
+
+            ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                    .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                    .setRegistrationState(
+                            NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
+                    .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                    .build());
+            doReturn(ss).when(phone).getServiceState();
+        }
     }
 
     private void initializeCommandInterfacesMock() {
@@ -1991,7 +2062,7 @@
         doReturn(true).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired();
     }
 
-    private void setDefaultDataSubId(int defaultDataSub) throws Exception {
+    private void setDefaultDataSubId(int defaultDataSub) {
         mDefaultDataSub = defaultDataSub;
         doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
         for (Phone phone : mPhones) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java
index 5941f06..ad99eaf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java
@@ -35,12 +35,12 @@
 import android.net.TelephonyNetworkSpecifier;
 import android.os.Looper;
 import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArraySet;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.ISub;
 import com.android.internal.telephony.RadioConfig;
@@ -190,7 +190,8 @@
     private void createMockedTelephonyComponents() throws Exception {
         replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mPhoneSwitcher);
 
-        mTelephonyNetworkFactoryUT = new TelephonyNetworkFactory(Looper.myLooper(), mPhone);
+        mTelephonyNetworkFactoryUT = new TelephonyNetworkFactory(Looper.myLooper(), mPhone,
+                mFeatureFlags);
         final ArgumentCaptor<NetworkProvider> providerCaptor =
                 ArgumentCaptor.forClass(NetworkProvider.class);
         verify(mConnectivityManager).registerNetworkProvider(providerCaptor.capture());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java
index 9752885..26a9fde 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java
@@ -87,6 +87,24 @@
             .setMaxConnsTime(789)
             .build();
 
+    private static final ApnSetting RCS_APN_SETTING = new ApnSetting.Builder()
+            .setId(2165)
+            .setOperatorNumeric("12345")
+            .setEntryName("rcs")
+            .setApnName("rcs")
+            .setUser("user")
+            .setPassword("passwd")
+            .setApnTypeBitmask(ApnSetting.TYPE_RCS)
+            .setProtocol(ApnSetting.PROTOCOL_IPV6)
+            .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
+            .setCarrierEnabled(true)
+            .setNetworkTypeBitmask(0)
+            .setProfileId(1234)
+            .setMaxConns(321)
+            .setWaitTime(456)
+            .setMaxConnsTime(789)
+            .build();
+
     @Before
     public void setUp() throws Exception {
         logd("TelephonyNetworkRequestTest +Setup!");
@@ -211,6 +229,20 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                 .build();
         assertThat(internetRequest.canBeSatisfiedBy(caps)).isTrue();
+
+        TelephonyNetworkRequest rcsRequest = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
+                        .build(), mPhone);
+        caps = new NetworkCapabilities.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .build();
+        assertThat(rcsRequest.canBeSatisfiedBy(caps)).isTrue();
     }
 
     @Test
@@ -223,17 +255,25 @@
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
                         .build(), mPhone);
+        TelephonyNetworkRequest rcsRequest = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
+                        .build(), mPhone);
         DataProfile internetDataProfile = new DataProfile.Builder()
                 .setApnSetting(INTERNET_APN_SETTING)
                 .build();
         DataProfile mmsDataProfile = new DataProfile.Builder()
                 .setApnSetting(MMS_APN_SETTING)
                 .build();
+        DataProfile rcsDataProfile = new DataProfile.Builder()
+                .setApnSetting(RCS_APN_SETTING)
+                .build();
 
         assertThat(internetRequest.canBeSatisfiedBy(internetDataProfile)).isTrue();
         assertThat(internetRequest.canBeSatisfiedBy(mmsDataProfile)).isFalse();
         assertThat(mmsRequest.canBeSatisfiedBy(internetDataProfile)).isFalse();
         assertThat(mmsRequest.canBeSatisfiedBy(mmsDataProfile)).isTrue();
+        assertThat(rcsRequest.canBeSatisfiedBy(rcsDataProfile)).isTrue();
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
index ce59cc6..47f8ce2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
@@ -20,29 +20,46 @@
 import static android.telephony.DomainSelectionService.SCAN_TYPE_NO_PREFERENCE;
 import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.net.Uri;
 import android.os.AsyncResult;
-import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telecom.PhoneAccount;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.DomainSelectionService;
-import android.telephony.DomainSelector;
-import android.telephony.EmergencyRegResult;
-import android.telephony.TransportSelectorCallback;
-import android.telephony.WwanSelectorCallback;
+import android.telephony.EmergencyRegistrationResult;
+import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.CallFailCause;
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorCallback;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
+import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
 
 import org.junit.After;
 import org.junit.Before;
@@ -50,12 +67,12 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -72,105 +89,95 @@
         super.setUp(this.getClass().getSimpleName());
 
         mDomainSelectionController = Mockito.mock(DomainSelectionController.class);
+        doReturn(true).when(mDomainSelectionController).selectDomain(any(), any());
         mConnectionCallback =
                 Mockito.mock(DomainSelectionConnection.DomainSelectionConnectionCallback.class);
     }
 
     @After
     public void tearDown() throws Exception {
+        mDsc.finishSelection();
         mDsc = null;
         super.tearDown();
     }
 
     @Test
     @SmallTest
-    public void testTransportSelectorCallback() {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+    public void testTransportSelectorCallback() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
                 mDomainSelectionController);
 
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
 
         assertNotNull(transportCallback);
     }
 
     @Test
     @SmallTest
-    public void testSelectDomain() {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+    public void testSelectDomain() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
                 mDomainSelectionController);
 
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
 
         DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
                 mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
-                false, 0, null, null, null, null);
+                false, 0, TELECOM_CALL_ID1, null, null, null);
 
         mDsc.selectDomain(attr);
 
-        verify(mDomainSelectionController).selectDomain(any(), eq(transportCallback));
-    }
-
-    @Test
-    @SmallTest
-    public void testWwanSelectorCallback() throws Exception {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
-                mDomainSelectionController);
-
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
-
-        assertNotNull(transportCallback);
-
-        WwanSelectorCallback wwanCallback = null;
-        wwanCallback = transportCallback.onWwanSelected();
-
-        assertNotNull(wwanCallback);
+        verify(mDomainSelectionController).selectDomain(eq(attr), eq(transportCallback));
     }
 
     @Test
     @SmallTest
     public void testWwanSelectorCallbackAsync() throws Exception {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
                 mDomainSelectionController);
-        replaceInstance(DomainSelectionConnection.class, "mWwanSelectedExecutor",
-                mDsc, new Executor() {
-                    public void execute(Runnable command) {
-                        command.run();
-                    }
-                });
 
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
 
         assertNotNull(transportCallback);
 
-        Consumer<WwanSelectorCallback> consumer = Mockito.mock(Consumer.class);
-        transportCallback.onWwanSelected(consumer);
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
 
-        verify(consumer).accept(any());
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
     }
 
     @Test
     @SmallTest
     public void testWwanSelectorCallbackOnRequestEmergencyNetworkScan() throws Exception {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
                 mDomainSelectionController);
 
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
 
         assertNotNull(transportCallback);
 
-        WwanSelectorCallback wwanCallback = transportCallback.onWwanSelected();
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
 
         assertNotNull(wwanCallback);
 
-        replaceInstance(DomainSelectionConnection.class, "mLooper",
-                mDsc, mTestableLooper.getLooper());
-        List<Integer> preferredNetworks = new ArrayList<>();
-        preferredNetworks.add(EUTRAN);
-        preferredNetworks.add(UTRAN);
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
         int scanType = SCAN_TYPE_NO_PREFERENCE;
-        Consumer<EmergencyRegResult> consumer = Mockito.mock(Consumer.class);
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
 
-        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType, null, consumer);
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback);
+        processAllMessages();
 
         ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
         ArgumentCaptor<Integer> eventCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -188,80 +195,573 @@
 
         assertNotNull(handler);
 
-        doReturn(new Executor() {
-            public void execute(Runnable r) {
-                r.run();
-            }
-        }).when(mDomainSelectionController).getDomainSelectionServiceExecutor();
-        EmergencyRegResult regResult =
-                new EmergencyRegResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        EmergencyRegistrationResult regResult =
+                new EmergencyRegistrationResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
         handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
         processAllMessages();
 
-        verify(consumer).accept(eq(regResult));
+        verify(resultCallback).onComplete(eq(regResult));
+        verify(mPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
     }
 
     @Test
     @SmallTest
     public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanAndCancel() throws Exception {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
                 mDomainSelectionController);
 
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
 
         assertNotNull(transportCallback);
 
-        WwanSelectorCallback wwanCallback = transportCallback.onWwanSelected();
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, null, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
 
         assertNotNull(wwanCallback);
 
-        replaceInstance(DomainSelectionConnection.class, "mLooper",
-                mDsc, mTestableLooper.getLooper());
-        CancellationSignal signal = new CancellationSignal();
-        wwanCallback.onRequestEmergencyNetworkScan(new ArrayList<>(),
-                SCAN_TYPE_NO_PREFERENCE, signal, Mockito.mock(Consumer.class));
+        wwanCallback.onRequestEmergencyNetworkScan(new int[] { }, SCAN_TYPE_NO_PREFERENCE,
+                false, Mockito.mock(IWwanSelectorResultCallback.class));
+        processAllMessages();
 
         verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any());
         verify(mPhone).triggerEmergencyNetworkScan(any(), anyInt(), any());
 
-        signal.cancel();
+        wwanCallback.onCancel();
+        processAllMessages();
 
         verify(mPhone).cancelEmergencyNetworkScan(eq(false), any());
     }
 
     @Test
-    @SmallTest
-    public void testDomainSelectorCancelSelection() throws Exception {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanWithResetScan()
+            throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
                 mDomainSelectionController);
 
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
 
         assertNotNull(transportCallback);
 
-        DomainSelector domainSelector = Mockito.mock(DomainSelector.class);
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                true, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+
+        verify(mPhone).cancelEmergencyNetworkScan(eq(true), msgCaptor.capture());
+
+        verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(msg);
+
+        AsyncResult unused = AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> eventCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(mPhone).registerForEmergencyNetworkScan(
+                handlerCaptor.capture(), eventCaptor.capture(), any());
+
+        int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
+
+        verify(mPhone).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks),
+                eq(scanType), any());
+
+        Handler handler = handlerCaptor.getValue();
+        int event = eventCaptor.getValue();
+
+        assertNotNull(handler);
+
+        EmergencyRegistrationResult regResult =
+                new EmergencyRegistrationResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
+        processAllMessages();
+
+        verify(resultCallback).onComplete(eq(regResult));
+    }
+
+    @Test
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanWithResetScanDoneAndCancel()
+            throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                true, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+
+        verify(mPhone).cancelEmergencyNetworkScan(eq(true), msgCaptor.capture());
+        verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(msg);
+
+        // Reset completes.
+        AsyncResult unused = AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify that scan is requested.
+        verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        // Cancele scan after reset completes.
+        wwanCallback.onCancel();
+        processAllMessages();
+
+        // Verify scan request is canceled.
+        verify(mPhone).cancelEmergencyNetworkScan(eq(false), any());
+        verify(mPhone, times(2)).cancelEmergencyNetworkScan(anyBoolean(), any());
+    }
+
+    @Test
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanWithResetScanAndCancel()
+            throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                true, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+
+        verify(mPhone).cancelEmergencyNetworkScan(eq(true), msgCaptor.capture());
+        verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(msg);
+
+        // Canceled before reset completes.
+        wwanCallback.onCancel();
+        processAllMessages();
+
+        // Verify there is no additional cancel.
+        verify(mPhone, times(1)).cancelEmergencyNetworkScan(anyBoolean(), any());
+
+        // Reset completes
+        AsyncResult unused = AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify there is no scan request after reset completes.
+        verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectorCancelSelection() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
         transportCallback.onCreated(domainSelector);
 
         mDsc.cancelSelection();
 
-        verify(domainSelector).cancelSelection();
+        verify(domainSelector).finishSelection();
     }
 
     @Test
     @SmallTest
     public void testDomainSelectorReselectDomain() throws Exception {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
                 mDomainSelectionController);
 
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
 
         assertNotNull(transportCallback);
 
-        DomainSelector domainSelector = Mockito.mock(DomainSelector.class);
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
         transportCallback.onCreated(domainSelector);
 
         DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
                 mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, CallFailCause.ERROR_UNSPECIFIED, TELECOM_CALL_ID1, null, null, null);
+
+        CompletableFuture<Integer> future = mDsc.reselectDomain(attr);
+
+        assertNotNull(future);
+        assertFalse(future.isDone());
+
+        verify(domainSelector).reselectDomain(eq(attr));
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectorFinishSelection() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        mDsc.finishSelection();
+
+        verify(domainSelector).finishSelection();
+    }
+
+    @Test
+    @SmallTest
+    public void testQualifiedNetworkTypesChanged() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        List<QualifiedNetworks> networksList = new ArrayList<>();
+
+        assertThat(mDsc.getPreferredTransport(ApnSetting.TYPE_EMERGENCY, networksList))
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        networksList.add(new QualifiedNetworks(ApnSetting.TYPE_EMERGENCY,
+                new int[]{ AccessNetworkType.IWLAN }));
+
+        assertThat(mDsc.getPreferredTransport(ApnSetting.TYPE_EMERGENCY, networksList))
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        networksList.clear();
+        networksList.add(new QualifiedNetworks(ApnSetting.TYPE_EMERGENCY,
+                new int[]{ AccessNetworkType.EUTRAN }));
+
+        assertThat(mDsc.getPreferredTransport(ApnSetting.TYPE_EMERGENCY, networksList))
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoteDomainSelectorRebindingService() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, null, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        verify(mDomainSelectionController, times(1)).selectDomain(eq(attr), eq(transportCallback));
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        // Detect failure
+        mDsc.onServiceDisconnected();
+
+        verify(mDomainSelectionController, times(1)).selectDomain(any(), eq(transportCallback));
+
+        // Waiting for timeout
+        processAllFutureMessages();
+
+        verify(mDomainSelectionController).removeConnection(eq(mDsc));
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoteDomainSelectorRebindingServiceWhenScanning() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        // 1st scan request from remote service
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> eventCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(mPhone).registerForEmergencyNetworkScan(
+                handlerCaptor.capture(), eventCaptor.capture(), any());
+
+        int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
+
+        verify(mPhone, times(1)).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks),
+                eq(scanType), any());
+        verify(mPhone, times(1)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        Handler handler = handlerCaptor.getValue();
+        int event = eventCaptor.getValue();
+
+        assertNotNull(handler);
+
+        mDsc.onServiceDisconnected();
+
+        assertTrue(mDsc.isWaitingForServiceBinding());
+
+        verify(mDomainSelectionController, times(1)).selectDomain(eq(attr), eq(transportCallback));
+
+        // reconnected to service
+        transportCallback.onCreated(domainSelector);
+
+        wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        // 2nd scan request
+        IWwanSelectorResultCallback resultCallback2 =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback2);
+        processAllMessages();
+
+        // Verify that triggerEmergencyNetworkScan isn't called
+        verify(mPhone, times(1)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        // Result received
+        EmergencyRegistrationResult regResult =
+                new EmergencyRegistrationResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
+        processAllMessages();
+
+        // Verify that triggerEmergencyNetworkScan isn't called
+        verify(mPhone, times(1)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+        verify(resultCallback, times(0)).onComplete(any());
+        verify(resultCallback2, times(1)).onComplete(eq(regResult));
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoteDomainSelectorRebindingServiceAfterScanCompleted() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, null, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        // 1st scan request from remote service
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> eventCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(mPhone).registerForEmergencyNetworkScan(
+                handlerCaptor.capture(), eventCaptor.capture(), any());
+
+        int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
+
+        verify(mPhone, times(1)).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks),
+                eq(scanType), any());
+
+        Handler handler = handlerCaptor.getValue();
+        int event = eventCaptor.getValue();
+
+        assertNotNull(handler);
+
+        mDsc.onServiceDisconnected();
+
+        verify(mDomainSelectionController, times(1)).selectDomain(eq(attr), eq(transportCallback));
+
+        // Result received
+        EmergencyRegistrationResult regResult =
+                new EmergencyRegistrationResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
+        processAllMessages();
+
+        verify(resultCallback, times(0)).onComplete(any());
+
+        // reconnected to service
+        transportCallback.onCreated(domainSelector);
+
+        wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        // 2nd scan request
+        IWwanSelectorResultCallback resultCallback2 =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback2);
+        processAllMessages();
+
+        // Verify that triggerEmergencyNetworkScan is called
+        verify(mPhone, times(2)).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        // Result received
+        regResult =
+                new EmergencyRegistrationResult(EUTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
+        processAllMessages();
+
+        verify(resultCallback, times(0)).onComplete(any());
+        verify(resultCallback2, times(1)).onComplete(eq(regResult));
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoteDomainSelectorRebindServiceWhenReselect() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, null, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        verify(mDomainSelectionController, times(1)).selectDomain(eq(attr), eq(transportCallback));
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                throw new RemoteException();
+            }
+        }).when(domainSelector).reselectDomain(any());
+        transportCallback.onCreated(domainSelector);
+
+        attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
                 false, CallFailCause.ERROR_UNSPECIFIED, null, null, null, null);
 
         CompletableFuture<Integer> future = mDsc.reselectDomain(attr);
@@ -269,31 +769,24 @@
         assertNotNull(future);
         assertFalse(future.isDone());
 
-        verify(domainSelector).reselectDomain(any());
+        verify(domainSelector).reselectDomain(eq(attr));
+        verify(mDomainSelectionController, times(1)).selectDomain(any(), eq(transportCallback));
     }
 
-    @Test
-    @SmallTest
-    public void testDomainSelectorFinishSelection() throws Exception {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
-                mDomainSelectionController);
-
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
-
-        assertNotNull(transportCallback);
-
-        DomainSelector domainSelector = Mockito.mock(DomainSelector.class);
-        transportCallback.onCreated(domainSelector);
-
-        mDsc.finishSelection();
-
-        verify(domainSelector).finishSelection();
+    private DomainSelectionConnection createConnection(Phone phone, int selectorType,
+            boolean isEmergency, DomainSelectionController controller) throws Exception {
+        DomainSelectionConnection dsc = new DomainSelectionConnection(phone,
+                selectorType, isEmergency, controller);
+        dsc.setTestMode(true);
+        replaceInstance(DomainSelectionConnection.class, "mLooper",
+                dsc, mTestableLooper.getLooper());
+        return dsc;
     }
 
     private DomainSelectionService.SelectionAttributes getSelectionAttributes(
             int slotId, int subId, int selectorType, boolean isEmergency,
             boolean exited, int callFailCause, String callId, String number,
-            ImsReasonInfo imsReasonInfo, EmergencyRegResult regResult) {
+            ImsReasonInfo imsReasonInfo, EmergencyRegistrationResult regResult) {
         DomainSelectionService.SelectionAttributes.Builder builder =
                 new DomainSelectionService.SelectionAttributes.Builder(
                         slotId, subId, selectorType)
@@ -302,10 +795,26 @@
                 .setCsDisconnectCause(callFailCause);
 
         if (callId != null) builder.setCallId(callId);
-        if (number != null) builder.setNumber(number);
+        if (number != null) {
+            builder.setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
+        }
         if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo);
-        if (regResult != null) builder.setEmergencyRegResult(regResult);
+        if (regResult != null) builder.setEmergencyRegistrationResult(regResult);
 
         return builder.build();
     }
+
+    private IWwanSelectorCallback onWwanSelected(ITransportSelectorCallback transportCallback)
+            throws Exception {
+        ITransportSelectorResultCallback cb = Mockito.mock(ITransportSelectorResultCallback.class);
+        transportCallback.onWwanSelectedAsync(cb);
+        processAllMessages();
+
+        ArgumentCaptor<IWwanSelectorCallback> callbackCaptor =
+                ArgumentCaptor.forClass(IWwanSelectorCallback.class);
+
+        verify(cb).onCompleted(callbackCaptor.capture());
+
+        return callbackCaptor.getValue();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionControllerTest.java
new file mode 100644
index 0000000..f5ccdd6
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionControllerTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2023 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.domainselection;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telephony.DomainSelectionService;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.telephony.IDomainSelectionServiceController;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for DomainSelectionController
+ */
+@RunWith(AndroidJUnit4.class)
+public class DomainSelectionControllerTest extends TelephonyTest {
+
+    private static final DomainSelectionController.BindRetry BIND_RETRY =
+            new DomainSelectionController.BindRetry() {
+                @Override
+                public long getStartDelay() {
+                    return 50;
+                }
+
+                @Override
+                public long getMaximumDelay() {
+                    return 1000;
+                }
+            };
+
+    // Mocked classes
+    IDomainSelectionServiceController mMockServiceControllerBinder;
+    Context mMockContext;
+
+    private final ComponentName mTestComponentName = new ComponentName("TestPkg",
+            "DomainSelectionControllerTest");
+    private Handler mHandler;
+    private DomainSelectionController mTestController;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(this.getClass().getSimpleName());
+
+        mMockContext = mock(Context.class);
+        mMockServiceControllerBinder = mock(IDomainSelectionServiceController.class);
+        mTestController = new DomainSelectionController(mMockContext,
+                Looper.getMainLooper(), BIND_RETRY);
+        mHandler = mTestController.getHandlerForTest();
+
+        when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true);
+    }
+
+
+    @After
+    public void tearDown() throws Exception {
+        mTestController.stopBackoffTimer();
+        waitForHandlerAction(mHandler, 1000);
+        mTestController = null;
+        super.tearDown();
+    }
+
+    /**
+     * Tests that Context.bindService is called with the correct parameters when we call bind.
+     */
+    @SmallTest
+    @Test
+    public void testBindService() {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+        assertTrue(mTestController.bind(mTestComponentName));
+
+        int expectedFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                | Context.BIND_IMPORTANT;
+
+        verify(mMockContext).bindService(intentCaptor.capture(), any(), eq(expectedFlags));
+
+        Intent testIntent = intentCaptor.getValue();
+
+        assertEquals(DomainSelectionService.SERVICE_INTERFACE, testIntent.getAction());
+        assertEquals(mTestComponentName, testIntent.getComponent());
+    }
+
+    /**
+     * Verify that if bind is called multiple times, we only call bindService once.
+     */
+    @SmallTest
+    @Test
+    public void testBindFailureWhenBound() {
+        bindAndConnectService();
+
+        // already bound, should return false
+        assertFalse(mTestController.bind(mTestComponentName));
+
+        verify(mMockContext, times(1)).bindService(any(), any(), anyInt());
+    }
+
+    /**
+     * Tests that when unbind is called while the DomainSelectionService is disconnected,
+     * we still handle unbinding to the service correctly.
+     */
+    @SmallTest
+    @Test
+    public void testBindServiceAndConnectedDisconnectedUnbind() throws RemoteException {
+        ServiceConnection conn = bindAndConnectService();
+
+        conn.onServiceDisconnected(mTestComponentName);
+
+        long delay = mTestController.getBindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+
+        mTestController.unbind();
+        verify(mMockContext).unbindService(eq(conn));
+    }
+
+    /**
+     * Tests DomainSelectionController callbacks are properly called when a DomainSelectionService
+     * is bound and subsequently unbound.
+     */
+    @SmallTest
+    @Test
+    public void testBindServiceBindUnbind() throws RemoteException {
+        ServiceConnection conn = bindAndConnectService();
+
+        mTestController.unbind();
+
+        verify(mMockContext).unbindService(eq(conn));
+    }
+
+    /**
+     * Ensures that imsServiceFeatureRemoved is called when the binder dies in another process.
+     */
+    @SmallTest
+    @Test
+    public void testBindServiceAndBinderDied() throws RemoteException {
+        ServiceConnection conn = bindAndConnectService();
+
+        conn.onBindingDied(null /*null*/);
+
+        long delay = BIND_RETRY.getStartDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+        verify(mMockContext).unbindService(eq(conn));
+    }
+
+    /**
+     * Ensures that imsServiceBindPermanentError is called when the binder returns null.
+     */
+    @SmallTest
+    @Test
+    public void testBindServiceAndReturnedNull() throws RemoteException {
+        bindAndNullServiceError();
+
+        long delay = mTestController.getBindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+    }
+
+    /**
+     * Verifies that the DomainSelectionController automatically tries to bind again after
+     * an untimely binder death.
+     */
+    @SmallTest
+    @Test
+    public void testAutoBindAfterBinderDied() throws RemoteException {
+        ServiceConnection conn = bindAndConnectService();
+
+        conn.onBindingDied(null /*null*/);
+
+        long delay = BIND_RETRY.getStartDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+        // The service should autobind after rebind event occurs
+        verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
+    }
+
+    /**
+     * Due to a bug in ServiceConnection, we will sometimes receive a null binding after the binding
+     * dies. Ignore null binding in this case.
+     */
+    @SmallTest
+    @Test
+    public void testAutoBindAfterBinderDiedIgnoreNullBinding() throws RemoteException {
+        ServiceConnection conn = bindAndConnectService();
+
+        conn.onBindingDied(null);
+        // null binding should be ignored in this case.
+        conn.onNullBinding(null);
+
+        long delay = BIND_RETRY.getStartDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+        // The service should autobind after rebind event occurs
+        verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
+    }
+
+    /**
+     * Ensure that bindService has only been called once before automatic rebind occurs.
+     */
+    @SmallTest
+    @Test
+    public void testNoAutoBindBeforeTimeout() throws RemoteException {
+        ServiceConnection conn = bindAndConnectService();
+
+        conn.onBindingDied(null /*null*/);
+
+        // Be sure that there are no binds before the RETRY_TIMEOUT expires
+        verify(mMockContext, times(1)).bindService(any(), any(), anyInt());
+    }
+
+    /**
+     * Ensure that calling unbind stops automatic rebind from occurring.
+     */
+    @SmallTest
+    @Test
+    public void testUnbindCauseAutoBindCancelAfterBinderDied() throws RemoteException {
+        ServiceConnection conn = bindAndConnectService();
+
+        conn.onBindingDied(null /*null*/);
+        mTestController.unbind();
+
+        long delay = mTestController.getBindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+
+        // Unbind should stop the autobind from occurring.
+        verify(mMockContext, times(1)).bindService(any(), any(), anyInt());
+    }
+
+    private void bindAndNullServiceError() {
+        ServiceConnection connection = bindService(mTestComponentName);
+        connection.onNullBinding(mTestComponentName);
+    }
+
+    private ServiceConnection bindAndConnectService() {
+        ServiceConnection connection = bindService(mTestComponentName);
+        IDomainSelectionServiceController.Stub controllerStub =
+                mock(IDomainSelectionServiceController.Stub.class);
+        when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder);
+        connection.onServiceConnected(mTestComponentName, controllerStub);
+
+        long delay = mTestController.getBindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
+        return connection;
+    }
+
+    private ServiceConnection bindService(ComponentName testComponentName) {
+        ArgumentCaptor<ServiceConnection> serviceCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        assertTrue(mTestController.bind(testComponentName));
+        verify(mMockContext).bindService(any(), serviceCaptor.capture(), anyInt());
+        return serviceCaptor.getValue();
+    }
+
+    private void waitForHandlerActionDelayed(Handler h, long timeoutMillis, long delayMs) {
+        final CountDownLatch lock = new CountDownLatch(1);
+        h.postDelayed(lock::countDown, delayMs);
+        while (lock.getCount() > 0) {
+            try {
+                lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java
index 2c65b50..e0fb0bd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java
@@ -31,14 +31,18 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
 import android.content.Context;
-import android.telephony.DomainSelectionService;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.HalVersion;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyTest;
@@ -55,10 +59,24 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class DomainSelectionResolverTest extends TelephonyTest {
+    private static final String COMPONENT_NAME_STRING =
+            "com.android.dss/.TelephonyDomainSelectionService";
+    private static final ComponentName DSS_COMPONENT_NAME =
+            ComponentName.unflattenFromString(COMPONENT_NAME_STRING);
+    private static final String CLASS_NAME = "com.android.dss.TelephonyDomainSelectionService";
+    private static final String EMPTY_COMPONENT_NAME_STRING = "/" + CLASS_NAME;
+    private static final ComponentName EMPTY_COMPONENT_NAME =
+            ComponentName.unflattenFromString(EMPTY_COMPONENT_NAME_STRING);
+    private static final String NONE_COMPONENT_NAME_STRING =
+            DomainSelectionResolver.PACKAGE_NAME_NONE + "/" + CLASS_NAME;
+    private static final ComponentName NONE_COMPONENT_NAME =
+            ComponentName.unflattenFromString(NONE_COMPONENT_NAME_STRING);
+    private static final String OVERRIDDEN_COMPONENT_NAME_STRING = "test/" + CLASS_NAME;
+    private static final ComponentName OVERRIDDEN_COMPONENT_NAME =
+            ComponentName.unflattenFromString(OVERRIDDEN_COMPONENT_NAME_STRING);
     // Mock classes
     private DomainSelectionController mDsController;
     private DomainSelectionConnection mDsConnection;
-    private DomainSelectionService mDsService;
 
     private DomainSelectionResolver mDsResolver;
 
@@ -68,13 +86,11 @@
 
         mDsController = Mockito.mock(DomainSelectionController.class);
         mDsConnection = Mockito.mock(DomainSelectionConnection.class);
-        mDsService = Mockito.mock(DomainSelectionService.class);
     }
 
     @After
     public void tearDown() throws Exception {
         mDsResolver = null;
-        mDsService = null;
         mDsConnection = null;
         mDsController = null;
         super.tearDown();
@@ -83,11 +99,13 @@
     @Test
     @SmallTest
     public void testGetInstance() throws IllegalStateException {
+        DomainSelectionResolver.setDomainSelectionResolver(null);
+
         assertThrows(IllegalStateException.class, () -> {
             DomainSelectionResolver.getInstance();
         });
 
-        DomainSelectionResolver.make(mContext, true);
+        DomainSelectionResolver.make(mContext, COMPONENT_NAME_STRING);
         DomainSelectionResolver resolver = DomainSelectionResolver.getInstance();
 
         assertNotNull(resolver);
@@ -95,8 +113,8 @@
 
     @Test
     @SmallTest
-    public void testIsDomainSelectionSupportedWhenDeviceConfigDisabled() {
-        setUpResolver(false, RADIO_HAL_VERSION_2_1);
+    public void testIsDomainSelectionSupportedWhenComponentNameNotConfigured() {
+        setUpResolver(null, RADIO_HAL_VERSION_2_1);
 
         assertFalse(mDsResolver.isDomainSelectionSupported());
     }
@@ -104,7 +122,7 @@
     @Test
     @SmallTest
     public void testIsDomainSelectionSupportedWhenHalVersionLessThan20() {
-        setUpResolver(true, RADIO_HAL_VERSION_2_0);
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_0);
 
         assertFalse(mDsResolver.isDomainSelectionSupported());
     }
@@ -112,7 +130,7 @@
     @Test
     @SmallTest
     public void testIsDomainSelectionSupported() {
-        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
 
         assertTrue(mDsResolver.isDomainSelectionSupported());
     }
@@ -120,7 +138,7 @@
     @Test
     @SmallTest
     public void testGetDomainSelectionConnectionWhenNotInitialized() throws Exception {
-        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
 
         assertThrows(IllegalStateException.class, () -> {
             mDsResolver.getDomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true);
@@ -130,35 +148,132 @@
     @Test
     @SmallTest
     public void testGetDomainSelectionConnectionWhenPhoneNull() throws Exception {
-        setUpResolver(true, RADIO_HAL_VERSION_2_1);
-        mDsResolver.initialize(mDsService);
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        mDsResolver.initialize();
+        verify(mDsController).bind(eq(DSS_COMPONENT_NAME));
+
         assertNull(mDsResolver.getDomainSelectionConnection(null, SELECTOR_TYPE_CALLING, true));
     }
 
     @Test
     @SmallTest
-    public void testGetDomainSelectionConnectionWhenImsNotAvailable() throws Exception {
-        setUpResolver(true, RADIO_HAL_VERSION_2_1);
-        mDsResolver.initialize(mDsService);
-        when(mPhone.isImsAvailable()).thenReturn(false);
+    public void testGetDomainSelectionConnectionWhenImsPhoneNull() throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        mDsResolver.initialize();
+        verify(mDsController).bind(eq(DSS_COMPONENT_NAME));
 
+        when(mPhone.getImsPhone()).thenReturn(null);
         assertNull(mDsResolver.getDomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true));
     }
 
     @Test
     @SmallTest
-    public void testGetDomainSelectionConnection() throws Exception {
-        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+    public void testGetDomainSelectionConnectionWhenImsNotAvailable() throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
         setUpController();
-        mDsResolver.initialize(mDsService);
-        when(mPhone.isImsAvailable()).thenReturn(true);
+        mDsResolver.initialize();
+        verify(mDsController).bind(eq(DSS_COMPONENT_NAME));
 
+        when(mPhone.isImsAvailable()).thenReturn(false);
+        when(mPhone.getImsPhone()).thenReturn(mImsPhone);
+        assertNull(mDsResolver.getDomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetDomainSelectionConnectionWhenImsNotAvailableForEmergencyCall()
+            throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        mDsResolver.initialize();
+        verify(mDsController).bind(eq(DSS_COMPONENT_NAME));
+
+        when(mPhone.isImsAvailable()).thenReturn(false);
+        when(mPhone.getImsPhone()).thenReturn(mImsPhone);
+        assertNotNull(mDsResolver.getDomainSelectionConnection(mPhone,
+                SELECTOR_TYPE_CALLING, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetDomainSelectionConnection() throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        mDsResolver.initialize();
+        verify(mDsController).bind(eq(DSS_COMPONENT_NAME));
+
+        when(mPhone.isImsAvailable()).thenReturn(true);
+        when(mPhone.getImsPhone()).thenReturn(mImsPhone);
         assertNotNull(mDsResolver.getDomainSelectionConnection(
                 mPhone, SELECTOR_TYPE_CALLING, true));
     }
 
-    private void setUpResolver(boolean deviceConfigEnabled, HalVersion halVersion) {
-        mDsResolver = new DomainSelectionResolver(mContext, deviceConfigEnabled);
+    @Test
+    @SmallTest
+    public void testSetDomainSelectionServiceOverride() throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        mDsResolver.initialize();
+        mDsResolver.setDomainSelectionServiceOverride(OVERRIDDEN_COMPONENT_NAME);
+        verify(mDsController, never()).unbind();
+        verify(mDsController).bind(eq(OVERRIDDEN_COMPONENT_NAME));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDomainSelectionServiceOverrideWithoutInitialize() throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        assertFalse(mDsResolver.setDomainSelectionServiceOverride(OVERRIDDEN_COMPONENT_NAME));
+        verify(mDsController, never()).bind(eq(OVERRIDDEN_COMPONENT_NAME));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDomainSelectionServiceOverrideWithEmptyComponentName() throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        mDsResolver.initialize();
+        assertTrue(mDsResolver.setDomainSelectionServiceOverride(EMPTY_COMPONENT_NAME));
+        verify(mDsController).unbind();
+        verify(mDsController, never()).bind(eq(EMPTY_COMPONENT_NAME));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDomainSelectionServiceOverrideWithNoneComponentName() throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        mDsResolver.initialize();
+        assertTrue(mDsResolver.setDomainSelectionServiceOverride(NONE_COMPONENT_NAME));
+        verify(mDsController).unbind();
+        verify(mDsController, never()).bind(eq(NONE_COMPONENT_NAME));
+    }
+
+    @Test
+    @SmallTest
+    public void testClearDomainSelectionServiceOverride() throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        mDsResolver.initialize();
+        mDsResolver.clearDomainSelectionServiceOverride();
+        verify(mDsController).unbind();
+        verify(mDsController, times(2)).bind(eq(DSS_COMPONENT_NAME));
+    }
+
+    @Test
+    @SmallTest
+    public void testClearDomainSelectionServiceOverrideWithoutInitialize() throws Exception {
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        assertFalse(mDsResolver.clearDomainSelectionServiceOverride());
+        verify(mDsController, never()).unbind();
+    }
+
+    private void setUpResolver(String flattenedComponentName, HalVersion halVersion) {
+        mDsResolver = new DomainSelectionResolver(mContext, flattenedComponentName);
         when(mPhone.getHalVersion(eq(HAL_SERVICE_NETWORK))).thenReturn(halVersion);
     }
 
@@ -166,8 +281,7 @@
         mDsResolver.setDomainSelectionControllerFactory(
                 new DomainSelectionResolver.DomainSelectionControllerFactory() {
                     @Override
-                    public DomainSelectionController create(Context context,
-                            DomainSelectionService service) {
+                    public DomainSelectionController create(Context context) {
                         return mDsController;
                     }
                 });
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
index 0c64b82..f893c35 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
@@ -17,7 +17,6 @@
 
 import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
 import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
-import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
 import static android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
@@ -38,25 +37,36 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.DomainSelectionService;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.TransportSelectorCallback;
-import android.telephony.WwanSelectorCallback;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.telephony.data.ApnSetting;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.telephony.ITransportSelectorCallback;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.AccessNetworksManager;
+import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 
 @RunWith(AndroidTestingRunner.class)
@@ -69,7 +79,7 @@
     private DomainSelectionConnection.DomainSelectionConnectionCallback mConnectionCallback;
     private EmergencyCallDomainSelectionConnection mEcDsc;
     private AccessNetworksManager mAnm;
-    private TransportSelectorCallback mTransportCallback;
+    private ITransportSelectorCallback mTransportCallback;
     private EmergencyStateTracker mEmergencyStateTracker;
 
     @Before
@@ -77,6 +87,7 @@
         super.setUp(this.getClass().getSimpleName());
 
         mDomainSelectionController = Mockito.mock(DomainSelectionController.class);
+        doReturn(true).when(mDomainSelectionController).selectDomain(any(), any());
         mConnectionCallback =
                 Mockito.mock(DomainSelectionConnection.DomainSelectionConnectionCallback.class);
         mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
@@ -84,6 +95,9 @@
         doReturn(mAnm).when(mPhone).getAccessNetworksManager();
         mEcDsc = new EmergencyCallDomainSelectionConnection(mPhone,
                 mDomainSelectionController, mEmergencyStateTracker);
+        mEcDsc.setTestMode(true);
+        replaceInstance(DomainSelectionConnection.class, "mLooper",
+                mEcDsc, mTestableLooper.getLooper());
         mTransportCallback = mEcDsc.getTransportSelectorCallback();
     }
 
@@ -96,11 +110,11 @@
     @Test
     @SmallTest
     public void testSelectDomainWifi() throws Exception {
-        doReturn(TRANSPORT_TYPE_WLAN).when(mAnm).getPreferredTransport(anyInt());
+        doReturn(TRANSPORT_TYPE_WWAN).when(mAnm).getPreferredTransport(anyInt());
         replaceInstance(EmergencyCallDomainSelectionConnection.class,
                 "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker);
 
-        EmergencyRegResult regResult = new EmergencyRegResult(
+        EmergencyRegistrationResult regResult = new EmergencyRegistrationResult(
                 EUTRAN, REGISTRATION_STATE_UNKNOWN,
                 NetworkRegistrationInfo.DOMAIN_PS,
                 true, false, 0, 0, "", "", "");
@@ -108,7 +122,7 @@
         DomainSelectionService.SelectionAttributes attr =
                 EmergencyCallDomainSelectionConnection.getSelectionAttributes(
                         mPhone.getPhoneId(), mPhone.getSubId(), false,
-                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+                        TELECOM_CALL_ID1, "911", false, 0, null, regResult);
 
         CompletableFuture<Integer> future =
                 mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
@@ -116,10 +130,26 @@
         assertNotNull(future);
         assertFalse(future.isDone());
 
-        verify(mDomainSelectionController).selectDomain(any(), any());
+        verify(mDomainSelectionController).selectDomain(any(),
+                any(ITransportSelectorCallback.class));
 
         mTransportCallback.onWlanSelected(true);
 
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> msgCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mAnm).registerForQualifiedNetworksChanged(
+                handlerCaptor.capture(), msgCaptor.capture());
+
+        assertFalse(future.isDone());
+
+        List<QualifiedNetworks> networksList = new ArrayList<>();
+        networksList.add(new QualifiedNetworks(ApnSetting.TYPE_EMERGENCY,
+                new int[]{ AccessNetworkType.IWLAN }));
+        AsyncResult ar = new AsyncResult(null, networksList, null);
+        Handler handler = handlerCaptor.getValue();
+        Integer msg = msgCaptor.getValue();
+        handler.handleMessage(Message.obtain(handler, msg.intValue(), ar));
+
         assertTrue(future.isDone());
         assertEquals((long) DOMAIN_NON_3GPP_PS, (long) future.get());
         verify(mEmergencyStateTracker).onEmergencyTransportChanged(
@@ -133,7 +163,7 @@
         replaceInstance(EmergencyCallDomainSelectionConnection.class,
                 "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker);
 
-        EmergencyRegResult regResult = new EmergencyRegResult(
+        EmergencyRegistrationResult regResult = new EmergencyRegistrationResult(
                 UTRAN, REGISTRATION_STATE_UNKNOWN,
                 NetworkRegistrationInfo.DOMAIN_CS,
                 true, false, 0, 0, "", "", "");
@@ -141,7 +171,7 @@
         DomainSelectionService.SelectionAttributes attr =
                 EmergencyCallDomainSelectionConnection.getSelectionAttributes(
                         mPhone.getPhoneId(), mPhone.getSubId(), false,
-                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+                        TELECOM_CALL_ID1, "911", false, 0, null, regResult);
 
         CompletableFuture<Integer> future =
                 mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
@@ -149,13 +179,13 @@
         assertNotNull(future);
         assertFalse(future.isDone());
 
-        verify(mDomainSelectionController).selectDomain(any(), any());
+        verify(mDomainSelectionController).selectDomain(any(),
+                any(ITransportSelectorCallback.class));
 
-        WwanSelectorCallback wwanCallback = null;
-        wwanCallback = mTransportCallback.onWwanSelected();
+        IWwanSelectorCallback wwanCallback = onWwanSelected(mTransportCallback);
 
         assertFalse(future.isDone());
-        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+        verify(mEmergencyStateTracker).onEmergencyTransportChangedAndWait(
                 eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WWAN));
 
         wwanCallback.onDomainSelected(DOMAIN_CS, false);
@@ -171,7 +201,7 @@
         replaceInstance(EmergencyCallDomainSelectionConnection.class,
                 "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker);
 
-        EmergencyRegResult regResult = new EmergencyRegResult(
+        EmergencyRegistrationResult regResult = new EmergencyRegistrationResult(
                 EUTRAN, REGISTRATION_STATE_UNKNOWN,
                 NetworkRegistrationInfo.DOMAIN_PS,
                 true, true, 0, 0, "", "", "");
@@ -179,7 +209,7 @@
         DomainSelectionService.SelectionAttributes attr =
                 EmergencyCallDomainSelectionConnection.getSelectionAttributes(
                         mPhone.getPhoneId(), mPhone.getSubId(), false,
-                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+                        TELECOM_CALL_ID1, "911", false, 0, null, regResult);
 
         CompletableFuture<Integer> future =
                 mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
@@ -187,13 +217,13 @@
         assertNotNull(future);
         assertFalse(future.isDone());
 
-        verify(mDomainSelectionController).selectDomain(any(), any());
+        verify(mDomainSelectionController).selectDomain(any(),
+                any(ITransportSelectorCallback.class));
 
-        WwanSelectorCallback wwanCallback = null;
-        wwanCallback = mTransportCallback.onWwanSelected();
+        IWwanSelectorCallback wwanCallback = onWwanSelected(mTransportCallback);
 
         assertFalse(future.isDone());
-        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+        verify(mEmergencyStateTracker).onEmergencyTransportChangedAndWait(
                 eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WWAN));
 
         wwanCallback.onDomainSelected(DOMAIN_PS, true);
@@ -205,7 +235,7 @@
     @Test
     @SmallTest
     public void testOnSelectionTerminated() throws Exception {
-        EmergencyRegResult regResult = new EmergencyRegResult(
+        EmergencyRegistrationResult regResult = new EmergencyRegistrationResult(
                 EUTRAN, REGISTRATION_STATE_UNKNOWN,
                 NetworkRegistrationInfo.DOMAIN_PS,
                 true, true, 0, 0, "", "", "");
@@ -213,7 +243,7 @@
         DomainSelectionService.SelectionAttributes attr =
                 EmergencyCallDomainSelectionConnection.getSelectionAttributes(
                         mPhone.getPhoneId(), mPhone.getSubId(), false,
-                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+                        TELECOM_CALL_ID1, "911", false, 0, null, regResult);
 
         mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
         mTransportCallback.onSelectionTerminated(ERROR_UNSPECIFIED);
@@ -227,4 +257,18 @@
         mEcDsc.cancelSelection();
         verify(mAnm).unregisterForQualifiedNetworksChanged(any());
     }
+
+    private IWwanSelectorCallback onWwanSelected(ITransportSelectorCallback transportCallback)
+            throws Exception {
+        ITransportSelectorResultCallback cb = Mockito.mock(ITransportSelectorResultCallback.class);
+        transportCallback.onWwanSelectedAsync(cb);
+        processAllMessages();
+
+        ArgumentCaptor<IWwanSelectorCallback> callbackCaptor =
+                ArgumentCaptor.forClass(IWwanSelectorCallback.class);
+
+        verify(cb).onCompleted(callbackCaptor.capture());
+
+        return callbackCaptor.getValue();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java
index 25ccecb..4f63be0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java
@@ -25,23 +25,29 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.DomainSelectionService;
-import android.telephony.DomainSelector;
 import android.telephony.NetworkRegistrationInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.telephony.data.ApnSetting;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.telephony.IDomainSelector;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.AccessNetworksManager;
+import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
 
 import org.junit.After;
@@ -51,6 +57,8 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -61,7 +69,7 @@
 public class EmergencySmsDomainSelectionConnectionTest extends TelephonyTest {
     private DomainSelectionController mDsController;
     private DomainSelectionConnection.DomainSelectionConnectionCallback mDscCallback;
-    private DomainSelector mDomainSelector;
+    private IDomainSelector mDomainSelector;
     private EmergencyStateTracker mEmergencyStateTracker;
 
     private Handler mHandler;
@@ -79,9 +87,10 @@
 
         mHandler = new Handler(Looper.myLooper());
         mDsController = Mockito.mock(DomainSelectionController.class);
+        doReturn(true).when(mDsController).selectDomain(any(), any());
         mDscCallback = Mockito.mock(
                 DomainSelectionConnection.DomainSelectionConnectionCallback.class);
-        mDomainSelector = Mockito.mock(DomainSelector.class);
+        mDomainSelector = Mockito.mock(IDomainSelector.class);
         mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
         mAnm = Mockito.mock(AccessNetworksManager.class);
         when(mPhone.getAccessNetworksManager()).thenReturn(mAnm);
@@ -151,9 +160,13 @@
         verify(mPhone).notifyEmergencyDomainSelected(
                 eq(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
 
+        List<QualifiedNetworks> networksList = new ArrayList<>();
+        networksList.add(new QualifiedNetworks(ApnSetting.TYPE_EMERGENCY,
+                new int[]{ AccessNetworkType.IWLAN }));
+        AsyncResult ar = new AsyncResult(null, networksList, null);
         Handler handler = handlerCaptor.getValue();
         Integer msg = msgCaptor.getValue();
-        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        handler.handleMessage(Message.obtain(handler, msg.intValue(), ar));
         processAllMessages();
 
         assertTrue(future.isDone());
@@ -214,9 +227,13 @@
         verify(mPhone).notifyEmergencyDomainSelected(
                 eq(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
 
+        List<QualifiedNetworks> networksList = new ArrayList<>();
+        networksList.add(new QualifiedNetworks(ApnSetting.TYPE_EMERGENCY,
+                new int[]{ AccessNetworkType.IWLAN }));
+        AsyncResult ar = new AsyncResult(null, networksList, null);
         Handler handler = handlerCaptor.getValue();
         Integer msg = msgCaptor.getValue();
-        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        handler.handleMessage(Message.obtain(handler, msg.intValue(), ar));
         processAllMessages();
 
         assertTrue(future.isDone());
@@ -273,9 +290,13 @@
         verify(mPhone).notifyEmergencyDomainSelected(
                 eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
 
+        List<QualifiedNetworks> networksList = new ArrayList<>();
+        networksList.add(new QualifiedNetworks(ApnSetting.TYPE_EMERGENCY,
+                new int[]{ AccessNetworkType.EUTRAN }));
+        AsyncResult ar = new AsyncResult(null, networksList, null);
         Handler handler = handlerCaptor.getValue();
         Integer msg = msgCaptor.getValue();
-        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        handler.handleMessage(Message.obtain(handler, msg.intValue(), ar));
         processAllMessages();
 
         assertTrue(future.isDone());
@@ -364,9 +385,13 @@
         verify(mPhone).notifyEmergencyDomainSelected(
                 eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
 
+        List<QualifiedNetworks> networksList = new ArrayList<>();
+        networksList.add(new QualifiedNetworks(ApnSetting.TYPE_EMERGENCY,
+                new int[]{ AccessNetworkType.EUTRAN }));
+        AsyncResult ar = new AsyncResult(null, networksList, null);
         Handler handler = handlerCaptor.getValue();
         Integer msg = msgCaptor.getValue();
-        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        handler.handleMessage(Message.obtain(handler, msg.intValue(), ar));
         processAllMessages();
 
         assertTrue(future.isDone());
@@ -419,7 +444,7 @@
 
         assertFalse(future.isDone());
         verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class));
-        verify(mDomainSelector).cancelSelection();
+        verify(mDomainSelector).finishSelection();
     }
 
     @Test
@@ -445,9 +470,13 @@
         verify(mPhone).notifyEmergencyDomainSelected(
                 eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
 
+        List<QualifiedNetworks> networksList = new ArrayList<>();
+        networksList.add(new QualifiedNetworks(ApnSetting.TYPE_EMERGENCY,
+                new int[]{ AccessNetworkType.EUTRAN }));
+        AsyncResult ar = new AsyncResult(null, networksList, null);
         Handler handler = handlerCaptor.getValue();
         Integer msg = msgCaptor.getValue();
-        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        handler.handleMessage(Message.obtain(handler, msg.intValue(), ar));
         processAllMessages();
         mDsConnection.finishSelection();
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java
index 0403232..72d8524 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java
@@ -31,24 +31,25 @@
 import static org.mockito.Mockito.verify;
 
 import android.telephony.DomainSelectionService;
-import android.telephony.TransportSelectorCallback;
-import android.telephony.WwanSelectorCallback;
 import android.telephony.ims.ImsReasonInfo;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.telephony.ITransportSelectorCallback;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.AccessNetworksManager;
-import com.android.internal.telephony.domainselection.DomainSelectionConnection;
-import com.android.internal.telephony.domainselection.DomainSelectionController;
-import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.CompletableFuture;
@@ -58,6 +59,7 @@
 public class NormalCallDomainSelectionConnectionTest extends TelephonyTest {
 
     private static final String TELECOM_CALL_ID1 = "TC1";
+    private static final int TEST_PHONE_ID = 111;
 
     @Mock
     private DomainSelectionController mMockDomainSelectionController;
@@ -66,7 +68,7 @@
     @Mock
     private AccessNetworksManager mMockAccessNetworksManager;
 
-    private TransportSelectorCallback mTransportCallback;
+    private ITransportSelectorCallback mTransportCallback;
     private NormalCallDomainSelectionConnection mNormalCallDomainSelectionConnection;
 
     @Before
@@ -74,8 +76,12 @@
         super.setUp(this.getClass().getSimpleName());
         MockitoAnnotations.initMocks(this);
         doReturn(mMockAccessNetworksManager).when(mPhone).getAccessNetworksManager();
+        doReturn(TEST_PHONE_ID).when(mPhone).getPhoneId();
+        doReturn(true).when(mMockDomainSelectionController).selectDomain(any(), any());
         mNormalCallDomainSelectionConnection =
                 new NormalCallDomainSelectionConnection(mPhone, mMockDomainSelectionController);
+        replaceInstance(DomainSelectionConnection.class, "mLooper",
+                mNormalCallDomainSelectionConnection, mTestableLooper.getLooper());
         mTransportCallback = mNormalCallDomainSelectionConnection.getTransportSelectorCallback();
     }
 
@@ -99,7 +105,8 @@
         assertNotNull(future);
         assertFalse(future.isDone());
 
-        verify(mMockDomainSelectionController).selectDomain(any(), any());
+        verify(mMockDomainSelectionController).selectDomain(any(),
+                any(ITransportSelectorCallback.class));
 
         mTransportCallback.onWlanSelected(false);
 
@@ -121,9 +128,10 @@
         assertNotNull(future);
         assertFalse(future.isDone());
 
-        verify(mMockDomainSelectionController).selectDomain(any(), any());
+        verify(mMockDomainSelectionController).selectDomain(any(),
+                any(ITransportSelectorCallback.class));
 
-        WwanSelectorCallback wwanCallback = mTransportCallback.onWwanSelected();
+        IWwanSelectorCallback wwanCallback = onWwanSelected(mTransportCallback);
 
         assertFalse(future.isDone());
         wwanCallback.onDomainSelected(DOMAIN_CS, false);
@@ -146,9 +154,10 @@
         assertNotNull(future);
         assertFalse(future.isDone());
 
-        verify(mMockDomainSelectionController).selectDomain(any(), any());
+        verify(mMockDomainSelectionController).selectDomain(any(),
+                any(ITransportSelectorCallback.class));
 
-        WwanSelectorCallback wwanCallback = mTransportCallback.onWwanSelected();
+        IWwanSelectorCallback wwanCallback = onWwanSelected(mTransportCallback);
 
         assertFalse(future.isDone());
         wwanCallback.onDomainSelected(DOMAIN_PS, false);
@@ -179,14 +188,48 @@
                 NormalCallDomainSelectionConnection.getSelectionAttributes(1, 2,
                         TELECOM_CALL_ID1, "123", false, 10, imsReasonInfo);
 
-        assertEquals(1, attributes.getSlotId());
-        assertEquals(2, attributes.getSubId());
+        assertEquals(1, attributes.getSlotIndex());
+        assertEquals(2, attributes.getSubscriptionId());
         assertEquals(TELECOM_CALL_ID1, attributes.getCallId());
-        assertEquals("123", attributes.getNumber());
+        assertEquals("123", attributes.getAddress().getSchemeSpecificPart());
         assertEquals(false, attributes.isVideoCall());
         assertEquals(false, attributes.isEmergency());
         assertEquals(SELECTOR_TYPE_CALLING, attributes.getSelectorType());
         assertEquals(10, attributes.getCsDisconnectCause());
         assertEquals(imsReasonInfo, attributes.getPsDisconnectCause());
     }
+
+    @Test
+    public void testGetSetMethods() throws Exception {
+        ImsReasonInfo imsReasonInfo = new ImsReasonInfo();
+        DomainSelectionService.SelectionAttributes attributes =
+                NormalCallDomainSelectionConnection.getSelectionAttributes(1, 2,
+                        TELECOM_CALL_ID1, "123", false, 0, imsReasonInfo);
+
+        mNormalCallDomainSelectionConnection
+                .createNormalConnection(attributes, mMockConnectionCallback);
+
+        mNormalCallDomainSelectionConnection.setDisconnectCause(100, 101,
+                "Test disconnect cause");
+        assertEquals(100, mNormalCallDomainSelectionConnection.getDisconnectCause());
+        assertEquals(101, mNormalCallDomainSelectionConnection.getPreciseDisconnectCause());
+        assertEquals("Test disconnect cause",
+                mNormalCallDomainSelectionConnection.getReasonMessage());
+        assertEquals(imsReasonInfo, mNormalCallDomainSelectionConnection.getImsReasonInfo());
+        assertEquals(TEST_PHONE_ID, mNormalCallDomainSelectionConnection.getPhoneId());
+    }
+
+    private IWwanSelectorCallback onWwanSelected(ITransportSelectorCallback transportCallback)
+            throws Exception {
+        ITransportSelectorResultCallback cb = Mockito.mock(ITransportSelectorResultCallback.class);
+        transportCallback.onWwanSelectedAsync(cb);
+        processAllMessages();
+
+        ArgumentCaptor<IWwanSelectorCallback> callbackCaptor =
+                ArgumentCaptor.forClass(IWwanSelectorCallback.class);
+
+        verify(cb).onCompleted(callbackCaptor.capture());
+
+        return callbackCaptor.getValue();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/OWNERS b/tests/telephonytests/src/com/android/internal/telephony/domainselection/OWNERS
new file mode 100644
index 0000000..2a76770
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/OWNERS
@@ -0,0 +1,9 @@
+# automatically inherit owners from fw/opt/telephony
+
+hwangoo@google.com
+forestchoi@google.com
+avinashmp@google.com
+mkoon@google.com
+seheele@google.com
+radhikaagrawal@google.com
+jdyou@google.com
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java
index e4afa79..5799dd8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java
@@ -22,20 +22,21 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.telephony.DisconnectCause;
 import android.telephony.DomainSelectionService;
-import android.telephony.DomainSelector;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.TransportSelectorCallback;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorCallback;
 import com.android.internal.telephony.Phone;
 
 import org.junit.After;
@@ -58,7 +59,7 @@
     @Mock private Phone mPhone;
     @Mock private DomainSelectionController mDsController;
     @Mock private DomainSelectionConnection.DomainSelectionConnectionCallback mDscCallback;
-    @Mock private DomainSelector mDomainSelector;
+    @Mock private IDomainSelector mDomainSelector;
 
     private Handler mHandler;
     private TestableLooper mTestableLooper;
@@ -69,6 +70,8 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        doReturn(true).when(mDsController).selectDomain(any(), any());
+
         HandlerThread handlerThread = new HandlerThread(
                 SmsDomainSelectionConnectionTest.class.getSimpleName());
         handlerThread.start();
@@ -107,7 +110,7 @@
                 mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
 
         assertNotNull(future);
-        verify(mDsController).selectDomain(eq(mDsAttr), any(TransportSelectorCallback.class));
+        verify(mDsController).selectDomain(eq(mDsAttr), any(ITransportSelectorCallback.class));
     }
 
     @Test
@@ -202,7 +205,7 @@
 
         mDsConnection.finishSelection();
 
-        verify(mDomainSelector).cancelSelection();
+        verify(mDomainSelector).finishSelection();
     }
 
     private void setUpTestableLooper() throws Exception {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
index 2a8e4e2..105f2bf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -23,6 +23,7 @@
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
+import static com.android.internal.telephony.emergency.EmergencyStateTracker.DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -52,15 +53,20 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.GsmCdmaPhone;
 import com.android.internal.telephony.Phone;
@@ -87,12 +93,10 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class EmergencyStateTrackerTest extends TelephonyTest {
-    private static final String TEST_CALL_ID = "TC@TEST1";
-    private static final String TEST_CALL_ID_2 = "TC@TEST2";
     private static final String TEST_SMS_ID = "1111";
     private static final String TEST_SMS_ID_2 = "2222";
     private static final long TEST_ECM_EXIT_TIMEOUT_MS = 500;
-    private static final EmergencyRegResult E_REG_RESULT = new EmergencyRegResult(
+    private static final EmergencyRegistrationResult E_REG_RESULT = new EmergencyRegistrationResult(
             EUTRAN, REGISTRATION_STATE_HOME, DOMAIN_CS_PS, true, true, 0, 1, "001", "01", "US");
 
     @Mock EmergencyStateTracker.PhoneFactoryProxy mPhoneFactoryProxy;
@@ -100,6 +104,8 @@
     @Mock EmergencyStateTracker.TelephonyManagerProxy mTelephonyManagerProxy;
     @Mock PhoneSwitcher mPhoneSwitcher;
     @Mock RadioOnHelper mRadioOnHelper;
+    @Mock android.telecom.Connection mTestConnection1;
+    @Mock android.telecom.Connection mTestConnection2;
 
     @Before
     public void setUp() throws Exception {
@@ -148,22 +154,93 @@
                 false /* isRadioOn */);
         setConfigForDdsSwitch(testPhone, null,
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "150");
+        ServiceState ss = mock(ServiceState.class);
+        doReturn(ss).when(mSST).getServiceState();
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .build();
+        doReturn(nri).when(ss).getNetworkRegistrationInfo(anyInt(), anyInt());
         // Spy is used to capture consumer in delayDialForDdsSwitch
         EmergencyStateTracker spyEst = spy(emergencyStateTracker);
-        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone, TEST_CALL_ID,
-                false);
+        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone,
+                mTestConnection1, false);
 
         // startEmergencyCall should trigger radio on
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
-                eq(false), eq(0));
-        // isOkToCall() should return true once radio is on
+                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
+        // isOkToCall() should return true when IN_SERVICE state
         assertFalse(callback.getValue()
                 .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
         when(mSST.isRadioOn()).thenReturn(true);
-        assertTrue(callback.getValue()
+        assertFalse(callback.getValue()
                 .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+
+        nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setRegistrationState(REGISTRATION_STATE_HOME)
+                .build();
+        doReturn(nri).when(ss).getNetworkRegistrationInfo(anyInt(), anyInt());
+
+        assertTrue(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
+        // Once radio on is complete, trigger delay dial
+        callback.getValue().onComplete(null, true);
+        ArgumentCaptor<Consumer<Boolean>> completeConsumer = ArgumentCaptor
+                .forClass(Consumer.class);
+        verify(spyEst).switchDdsDelayed(eq(testPhone), completeConsumer.capture());
+        verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(testPhone.getPhoneId()),
+                eq(150) /* extensionTime */, any());
+        // After dds switch completes successfully, set emergency mode
+        completeConsumer.getValue().accept(true);
+        verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any());
+    }
+
+    /**
+     * Test that the EmergencyStateTracker turns on radio, performs a DDS switch and sets emergency
+     * mode switch when we are not roaming and the carrier only supports SUPL over the data plane.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_radioOff_turnOnRadioTimeoutSwitchDdsAndSetEmergencyMode() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones and set radio off
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        setConfigForDdsSwitch(testPhone, null,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "150");
+        ServiceState ss = mock(ServiceState.class);
+        doReturn(ss).when(mSST).getServiceState();
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .build();
+        doReturn(nri).when(ss).getNetworkRegistrationInfo(anyInt(), anyInt());
+        // Spy is used to capture consumer in delayDialForDdsSwitch
+        EmergencyStateTracker spyEst = spy(emergencyStateTracker);
+        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+
+        // startEmergencyCall should trigger radio on
+        ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
+                .forClass(RadioOnStateListener.Callback.class);
+        verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
+                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
+        // onTimeout should return true when radion on
+        assertFalse(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+        assertFalse(callback.getValue()
+                .onTimeout(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+        when(mSST.isRadioOn()).thenReturn(true);
+
+        assertFalse(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+        assertTrue(callback.getValue()
+                .onTimeout(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
         // Once radio on is complete, trigger delay dial
         callback.getValue().onComplete(null, true);
         ArgumentCaptor<Consumer<Boolean>> completeConsumer = ArgumentCaptor
@@ -190,16 +267,93 @@
                 false /* isRadioOn */);
 
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
+
+        // startEmergencyCall should trigger radio on
+        ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
+                .forClass(RadioOnStateListener.Callback.class);
+        verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
+                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
+        // Verify future completes with DisconnectCause.POWER_OFF if radio not ready
+        CompletableFuture<Void> unused = future.thenAccept((result) -> {
+            assertEquals((Integer) result, (Integer) DisconnectCause.POWER_OFF);
+        });
+        callback.getValue().onComplete(null, false /* isRadioReady */);
+    }
+
+    /**
+     * Test that the EmergencyStateTracker turns off satellite modem, performs a DDS switch and
+     * sets emergency mode switch when we are not roaming and the carrier only supports SUPL over
+     * the data plane.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_satelliteEnabled_turnOnRadioSwitchDdsAndSetEmergencyMode() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones and set radio on
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                true /* isRadioOn */);
+        when(mSST.isRadioOn()).thenReturn(true);
+        // Satellite enabled
+        when(mSatelliteController.isSatelliteEnabled()).thenReturn(true);
+
+        setConfigForDdsSwitch(testPhone, null,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "150");
+        // Spy is used to capture consumer in delayDialForDdsSwitch
+        EmergencyStateTracker spyEst = spy(emergencyStateTracker);
+        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone,
+                mTestConnection1, false);
 
         // startEmergencyCall should trigger radio on
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
                 eq(false), eq(0));
+        // isOkToCall() should return true once satellite modem is off
+        assertFalse(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
+        when(mSatelliteController.isSatelliteEnabled()).thenReturn(false);
+        assertTrue(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
+        // Once radio on is complete, trigger delay dial
+        callback.getValue().onComplete(null, true);
+        ArgumentCaptor<Consumer<Boolean>> completeConsumer = ArgumentCaptor
+                .forClass(Consumer.class);
+        verify(spyEst).switchDdsDelayed(eq(testPhone), completeConsumer.capture());
+        verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(testPhone.getPhoneId()),
+                eq(150) /* extensionTime */, any());
+        // After dds switch completes successfully, set emergency mode
+        completeConsumer.getValue().accept(true);
+        verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any());
+    }
+
+    /**
+     * Test that if startEmergencyCall fails to turn off satellite modem, then it's future completes
+     * with {@link DisconnectCause#SATELLITE_ENABLED}.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_satelliteOffFails_returnsDisconnectCauseSatelliteEnabled() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones and set radio on
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                true /* isRadioOn */);
+        // Satellite enabled
+        when(mSatelliteController.isSatelliteEnabled()).thenReturn(true);
+
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+
+        // startEmergencyCall should trigger satellite modem off
+        ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
+                .forClass(RadioOnStateListener.Callback.class);
+        verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
+                eq(false), eq(0));
         // Verify future completes with DisconnectCause.POWER_OFF if radio not ready
         CompletableFuture<Void> unused = future.thenAccept((result) -> {
-            assertEquals((Integer) result, (Integer) DisconnectCause.POWER_OFF);
+            assertEquals((Integer) result, (Integer) DisconnectCause.SATELLITE_ENABLED);
         });
         callback.getValue().onComplete(null, false /* isRadioReady */);
     }
@@ -220,7 +374,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Radio already on so shouldn't trigger this
         verify(mRadioOnHelper, never()).triggerRadioOnAndListen(any(), anyBoolean(), any(),
@@ -245,7 +399,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // non-DDS supports SUPL, so no DDS switch
         verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
@@ -267,7 +421,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Is roaming, so no DDS switch
         verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
@@ -294,7 +448,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Verify DDS switch
         verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /* phoneId */,
@@ -322,7 +476,7 @@
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0");
 
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Verify DDS switch
         verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /* phoneId */,
@@ -331,7 +485,7 @@
 
     /**
      * Test that once EmergencyStateTracker handler receives set emergency mode done message it sets
-     * IsInEmergencyCall to true, sets LastEmergencyRegResult and completes future with
+     * IsInEmergencyCall to true, sets LastEmergencyRegistrationResult and completes future with
      * DisconnectCause.NOT_DISCONNECTED.
      */
     @Test
@@ -345,7 +499,7 @@
         setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Verify future completes with DisconnectCause.NOT_DISCONNECTED
         CompletableFuture<Void> unused = future.thenAccept((result) -> {
             assertEquals((Integer) result, (Integer) DisconnectCause.NOT_DISCONNECTED);
@@ -355,7 +509,7 @@
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyCall());
-        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
         verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
     }
 
@@ -375,13 +529,13 @@
         setUpAsyncResultForExitEmergencyMode(testPhone);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyCall());
@@ -402,15 +556,15 @@
                 /* isRadioOn= */ true);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // set domain
         emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_IMS,
-                TEST_CALL_ID);
+                mTestConnection1);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         // Make sure CS ECBM is true
         assertTrue(emergencyStateTracker.isInEcm());
@@ -432,15 +586,15 @@
                 /* isRadioOn= */ true);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // set domain
         emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_CDMA,
-                TEST_CALL_ID);
+                mTestConnection1);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         // Make sure IMS ECBM is true
         assertTrue(emergencyStateTracker.isInEcm());
@@ -464,15 +618,15 @@
         doReturn(PhoneConstants.PHONE_TYPE_GSM).when(testPhone).getPhoneType();
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // set domain
         emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_CDMA,
-                TEST_CALL_ID);
+                mTestConnection1);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInCdmaEcm());
@@ -492,7 +646,7 @@
                 /* isRadioOn= */ true );
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
@@ -501,6 +655,27 @@
     }
 
     /**
+     * Test that onEmergencyTransportChangedAndWait sets the new emergency mode.
+     */
+    @Test
+    @SmallTest
+    public void onEmergencyTransportChangedAndWait_setEmergencyMode() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phones
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Call startEmergencyCall() to set testPhone
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+
+        emergencyStateTracker.onEmergencyTransportChangedAndWait(
+                EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
+
+        verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any());
+    }
+
+    /**
      * Test that after endCall() is called, EmergencyStateTracker will enter ECM if the call was
      * ACTIVE and send related intents.
      */
@@ -515,15 +690,15 @@
                 /* isRadioOn= */ true);
         // Start emergency call then enter ECM
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, true);
 
         assertFalse(emergencyStateTracker.isInEcm());
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
         // Verify intents are sent that ECM is entered
@@ -550,13 +725,13 @@
                 /* isRadioOn= */ true);
         // Start emergency call then enter ECM
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Call does not reach ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.IDLE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.IDLE, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertFalse(emergencyStateTracker.isInEcm());
     }
@@ -577,15 +752,15 @@
         setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
         setUpAsyncResultForExitEmergencyMode(testPhone);
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
 
         processAllMessages();
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
 
@@ -597,6 +772,252 @@
     }
 
     /**
+     * Test that once endCall() for IMS call is called and we enter ECM, then we exit ECM
+     * after the specified timeout.
+     */
+    @Test
+    @SmallTest
+    public void endCall_entersEcm_thenExitsEcmAfterTimeoutImsCall() throws Exception {
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(testPhone);
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.onEmergencyCallDomainUpdated(
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
+        // Set ecm as supported
+        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
+
+        processAllMessages();
+
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+
+        Context mockContext = mock(Context.class);
+        replaceInstance(EmergencyStateTracker.class, "mContext",
+                emergencyStateTracker, mockContext);
+        processAllFutureMessages();
+
+        ArgumentCaptor<TelephonyCallback> callbackCaptor =
+                ArgumentCaptor.forClass(TelephonyCallback.class);
+
+        verify(mTelephonyManagerProxy).registerTelephonyCallback(eq(testPhone.getSubId()),
+                  any(), callbackCaptor.capture());
+
+        TelephonyCallback callback = callbackCaptor.getValue();
+
+        assertNotNull(callback);
+
+        // Verify exitEmergencyMode() is called after timeout
+        verify(testPhone).exitEmergencyMode(any(Message.class));
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        verify(mTelephonyManagerProxy).unregisterTelephonyCallback(eq(callback));
+    }
+
+    /**
+     * Test that startEmergencyCall() is called right after exiting ECM on the same slot.
+     */
+    @Test
+    @SmallTest
+    public void exitEcm_thenDialEmergencyCallOnTheSameSlotRightAfter() throws Exception {
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        emergencyStateTracker.setPdnDisconnectionTimeoutMs(0);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(testPhone);
+
+        verify(testPhone, times(0)).setEmergencyMode(anyInt(), any(Message.class));
+        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
+
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+        processAllMessages();
+
+        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
+
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.onEmergencyCallDomainUpdated(
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
+        // Set ecm as supported
+        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
+
+        processAllMessages();
+
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+
+        Context mockContext = mock(Context.class);
+        replaceInstance(EmergencyStateTracker.class, "mContext",
+                emergencyStateTracker, mockContext);
+        processAllFutureMessages();
+
+        ArgumentCaptor<TelephonyCallback> callbackCaptor =
+                ArgumentCaptor.forClass(TelephonyCallback.class);
+
+        verify(mTelephonyManagerProxy).registerTelephonyCallback(eq(testPhone.getSubId()),
+                  any(), callbackCaptor.capture());
+
+        TelephonyCallback callback = callbackCaptor.getValue();
+
+        assertNotNull(callback);
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        verify(mTelephonyManagerProxy, times(0)).unregisterTelephonyCallback(eq(callback));
+        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK),
+                any(Message.class));
+        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
+
+        replaceInstance(EmergencyStateTracker.class, "mContext", emergencyStateTracker, mContext);
+
+        // dial on the same slot
+        unused = emergencyStateTracker.startEmergencyCall(testPhone, mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        verify(mTelephonyManagerProxy, times(1)).unregisterTelephonyCallback(eq(callback));
+        verify(testPhone, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK),
+                any(Message.class));
+        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
+    }
+
+    /**
+     * Test that startEmergencyCall() is called right after exiting ECM on the other slot.
+     */
+    @Test
+    @SmallTest
+    public void exitEcm_thenDialEmergencyCallOnTheOtherSlotRightAfter() throws Exception {
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        emergencyStateTracker.setPdnDisconnectionTimeoutMs(0);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(testPhone);
+
+        verify(testPhone, times(0)).setEmergencyMode(anyInt(), any(Message.class));
+        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
+
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+        processAllMessages();
+
+        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
+
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.onEmergencyCallDomainUpdated(
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
+        // Set ecm as supported
+        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
+
+        processAllMessages();
+
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+
+        Context mockContext = mock(Context.class);
+        replaceInstance(EmergencyStateTracker.class, "mContext",
+                emergencyStateTracker, mockContext);
+        processAllFutureMessages();
+
+        ArgumentCaptor<TelephonyCallback> callbackCaptor =
+                ArgumentCaptor.forClass(TelephonyCallback.class);
+
+        verify(mTelephonyManagerProxy).registerTelephonyCallback(eq(testPhone.getSubId()),
+                  any(), callbackCaptor.capture());
+
+        TelephonyCallback callback = callbackCaptor.getValue();
+
+        assertNotNull(callback);
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        verify(mTelephonyManagerProxy, times(0)).unregisterTelephonyCallback(eq(callback));
+        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK),
+                any(Message.class));
+        verify(testPhone, times(0)).exitEmergencyMode(any(Message.class));
+
+        Phone phone1 = getPhone(1);
+        verify(phone1, times(0)).setEmergencyMode(anyInt(), any(Message.class));
+        verify(phone1, times(0)).exitEmergencyMode(any(Message.class));
+
+        replaceInstance(EmergencyStateTracker.class, "mContext", emergencyStateTracker, mContext);
+
+        // dial on the other slot
+        unused = emergencyStateTracker.startEmergencyCall(phone1, mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        verify(mTelephonyManagerProxy, times(1)).unregisterTelephonyCallback(eq(callback));
+        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(testPhone, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK),
+                any(Message.class));
+        verify(testPhone, times(1)).exitEmergencyMode(any(Message.class));
+        verify(phone1, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(phone1, times(0)).exitEmergencyMode(any(Message.class));
+    }
+
+    /**
+     * Test that once endCall() is called and we enter ECM, then we exit ECM when turning on
+     * airplane mode.
+     */
+    @Test
+    @SmallTest
+    public void endCall_entersEcm_thenExitsEcmWhenTurnOnAirplaneMode() {
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(testPhone);
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        // Set ecm as supported
+        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
+
+        processAllMessages();
+
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+
+        emergencyStateTracker.onCellularRadioPowerOffRequested();
+
+        // Verify exitEmergencyMode() is called.
+        verify(testPhone).exitEmergencyMode(any(Message.class));
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+    }
+
+    /**
      * Test that after exitEmergencyCallbackMode() is called, the correct intents are sent and
      * emergency mode is exited on the modem.
      */
@@ -613,16 +1034,16 @@
         setUpAsyncResultForExitEmergencyMode(testPhone);
         // Start emergency call then enter ECM
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
         emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
         // Set ecm as supported
         setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
         // End call to enter ECM
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         // verify ecbm states are correct
@@ -658,7 +1079,7 @@
         Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                 /* isRadioOn= */ true);
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         Handler handler = new Handler(Looper.getMainLooper());
         handler.post(() -> {
@@ -678,15 +1099,13 @@
         Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
                 /* isRadioOn= */ true);
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, true);
+                mTestConnection1, true);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
-        assertTrue(emergencyStateTracker.isInEmergencyCall());
-        // Expect: DisconnectCause#NOT_DISCONNECTED.
-        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
-                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
-        verify(phone0, never()).setEmergencyMode(anyInt(), any(Message.class));
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertFalse(future.isDone());
+        verify(phone0).setEmergencyMode(anyInt(), any(Message.class));
     }
 
     @Test
@@ -699,18 +1118,18 @@
         setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
         // First active call
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Second starting call
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID_2, false);
+                mTestConnection2, false);
 
         // Returns DisconnectCause#NOT_DISCONNECTED immediately.
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
@@ -729,22 +1148,22 @@
 
         // First active call
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEcm());
 
         // Second emergency call started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID_2, false);
+                mTestConnection2, false);
 
         // Returns DisconnectCause#NOT_DISCONNECTED immediately.
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
@@ -762,7 +1181,7 @@
 
         // First emergency call
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -772,7 +1191,7 @@
         // Second emergency call
         Phone phone1 = getPhone(1);
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone1,
-                TEST_CALL_ID_2, false);
+                mTestConnection2, false);
 
         // Returns DisconnectCause#ERROR_UNSPECIFIED immediately.
         assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
@@ -791,7 +1210,7 @@
 
         // First active call
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -800,15 +1219,15 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
 
         // Second emergency call started.
-        future = emergencyStateTracker.startEmergencyCall(phone0, TEST_CALL_ID_2, false);
+        future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection2, false);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         // Returns DisconnectCause#NOT_DISCONNECTED immediately.
@@ -817,7 +1236,7 @@
 
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WLAN);
-        emergencyStateTracker.endCall(TEST_CALL_ID_2);
+        emergencyStateTracker.endCall(mTestConnection2);
         processAllMessages();
 
         // At this time, ECM is still running so still in ECM.
@@ -829,6 +1248,34 @@
 
     @Test
     @SmallTest
+    public void testRecoverNormalInCellularWhenVoWiFiConnected() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phones
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Call startEmergencyCall() to set testPhone
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+
+        // Set emergency transport
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WLAN);
+
+        // Set call properties
+        emergencyStateTracker.onEmergencyCallPropertiesChanged(
+                android.telecom.Connection.PROPERTY_WIFI, mTestConnection1);
+
+        verify(testPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
+
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+
+        verify(testPhone, times(1)).cancelEmergencyNetworkScan(eq(true), any());
+    }
+
+    @Test
+    @SmallTest
     public void testStartEmergencySms() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -842,7 +1289,7 @@
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
         // Expect: DisconnectCause#NOT_DISCONNECTED.
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
@@ -863,12 +1310,12 @@
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
         // Expect: DisconnectCause#NOT_DISCONNECTED.
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+        emergencyStateTracker.endSms(TEST_SMS_ID, true);
 
         verify(phone0).exitEmergencyMode(any(Message.class));
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -897,7 +1344,7 @@
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
-        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WLAN), any(Message.class));
     }
 
@@ -973,14 +1420,14 @@
         setEcmSupportedConfig(phone0, true);
         // Emergency call is ended and the emergency callback is entered.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
@@ -1031,14 +1478,14 @@
         setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1058,7 +1505,7 @@
                 /* isRadioOn= */ true);
         // Emergency call is in progress.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1103,7 +1550,7 @@
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
         // Emergency call is being started.
-        future = emergencyStateTracker.startEmergencyCall(phone0, TEST_CALL_ID, false);
+        future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection1, false);
         processAllMessages();
 
         verify(phone0).exitEmergencyMode(any(Message.class));
@@ -1134,7 +1581,7 @@
 
         // Emergency call is being started.
         CompletableFuture<Integer> callFuture = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         assertFalse(smsFuture.isDone());
         assertFalse(callFuture.isDone());
@@ -1146,7 +1593,7 @@
         processAllMessages();
 
         // Exit emergency mode and set the emergency mode again by the call when the exit result
-        // is received for obtaining the latest EmergencyRegResult.
+        // is received for obtaining the latest EmergencyRegistrationResult.
         verify(phone0).exitEmergencyMode(any(Message.class));
         ArgumentCaptor<Message> callCaptor = ArgumentCaptor.forClass(Message.class);
         verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), callCaptor.capture());
@@ -1176,14 +1623,14 @@
         setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started using the different phone.
         Phone phone1 = getPhone(1);
@@ -1217,7 +1664,7 @@
         // Emergency call is being started using the different phone.
         Phone phone1 = getPhone(1);
         setUpAsyncResultForSetEmergencyMode(phone1, E_REG_RESULT);
-        future = emergencyStateTracker.startEmergencyCall(phone1, TEST_CALL_ID, false);
+        future = emergencyStateTracker.startEmergencyCall(phone1, mTestConnection1, false);
         processAllMessages();
 
         verify(phone0).exitEmergencyMode(any(Message.class));
@@ -1250,7 +1697,7 @@
         // Emergency call is being started using the different phone.
         Phone phone1 = getPhone(1);
         CompletableFuture<Integer> callFuture = emergencyStateTracker.startEmergencyCall(phone1,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         assertFalse(smsFuture.isDone());
         assertFalse(callFuture.isDone());
@@ -1262,7 +1709,7 @@
         processAllMessages();
 
         // Exit emergency mode and set the emergency mode again by the call when the exit result
-        // is received for obtaining the latest EmergencyRegResult.
+        // is received for obtaining the latest EmergencyRegistrationResult.
         verify(phone0).exitEmergencyMode(any(Message.class));
         ArgumentCaptor<Message> callCaptor = ArgumentCaptor.forClass(Message.class);
         verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), callCaptor.capture());
@@ -1294,14 +1741,14 @@
         setEcmSupportedConfig(phone0, false);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1311,11 +1758,11 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+        emergencyStateTracker.endSms(TEST_SMS_ID, true);
         processAllMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -1335,14 +1782,14 @@
         setEcmSupportedConfig(phone0, false);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1352,11 +1799,11 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+        emergencyStateTracker.endSms(TEST_SMS_ID, true);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -1376,14 +1823,14 @@
         setEcmSupportedConfig(phone0, true);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1393,13 +1840,13 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+        emergencyStateTracker.endSms(TEST_SMS_ID, true);
         processAllMessages();
 
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
@@ -1428,14 +1875,14 @@
         setEcmSupportedConfig(phone0, true);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1445,7 +1892,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1454,7 +1901,7 @@
 
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
-        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+        emergencyStateTracker.endSms(TEST_SMS_ID, true);
         processAllMessages();
 
         // Enter emergency callback mode and emergency mode changed by SMS end.
@@ -1484,14 +1931,14 @@
         setEcmSupportedConfig(phone0, true);
         // Emergency call is in active.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
 
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         // Emergency SMS is being started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
@@ -1501,11 +1948,11 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+        emergencyStateTracker.endSms(TEST_SMS_ID, true);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1521,6 +1968,250 @@
         verify(phone0).exitEmergencyMode(any(Message.class));
     }
 
+    @Test
+    @SmallTest
+    public void testSaveKeyEmergencyCallbackModeSupportedBool() {
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        when(phone.getSubId()).thenReturn(1);
+        setEcmSupportedConfig(phone, true);
+
+        EmergencyStateTracker testEst = setupEmergencyStateTracker(
+                false /* isSuplDdsSwitchRequiredForEmergencyCall */);
+
+        assertNotNull(testEst.startEmergencyCall(phone, mTestConnection1, false));
+
+        ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+        CarrierConfigManager cfgManager = (CarrierConfigManager) mContext
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+
+        verify(cfgManager).registerCarrierConfigChangeListener(any(),
+                listenerArgumentCaptor.capture());
+
+        CarrierConfigManager.CarrierConfigChangeListener carrierConfigChangeListener =
+                listenerArgumentCaptor.getAllValues().get(0);
+
+        // Verify carrier config for valid subscription
+        assertTrue(testEst.isEmergencyCallbackModeSupported());
+
+        // SIM removed
+        when(phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setEcmSupportedConfig(phone, false);
+
+        // Verify default config for invalid subscription
+        assertFalse(testEst.isEmergencyCallbackModeSupported());
+
+        // Insert SIM again
+        when(phone.getSubId()).thenReturn(1);
+        setEcmSupportedConfig(phone, true);
+
+        // onCarrierConfigChanged with valid subscription
+        carrierConfigChangeListener.onCarrierConfigChanged(
+                phone.getPhoneId(), phone.getSubId(),
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+
+        // SIM removed again
+        when(phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setEcmSupportedConfig(phone, false);
+
+        // onCarrierConfigChanged with invalid subscription
+        carrierConfigChangeListener.onCarrierConfigChanged(
+                phone.getPhoneId(), phone.getSubId(),
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+
+        // Verify saved config for valid subscription
+        assertTrue(testEst.isEmergencyCallbackModeSupported());
+
+        // Insert SIM again, but emergency callback mode not supported
+        when(phone.getSubId()).thenReturn(1);
+        setEcmSupportedConfig(phone, false);
+
+        // onCarrierConfigChanged with valid subscription
+        carrierConfigChangeListener.onCarrierConfigChanged(
+                phone.getPhoneId(), phone.getSubId(),
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+
+        // Verify carrier config for valid subscription
+        assertFalse(testEst.isEmergencyCallbackModeSupported());
+
+        // SIM removed again
+        when(phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setEcmSupportedConfig(phone, false);
+
+        // onCarrierConfigChanged with invalid subscription
+        carrierConfigChangeListener.onCarrierConfigChanged(
+                phone.getPhoneId(), phone.getSubId(),
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+
+        // Verify saved config for valid subscription
+        assertFalse(testEst.isEmergencyCallbackModeSupported());
+    }
+
+    /**
+     * Test that new emergency call is dialed while in emergency callback mode and completes.
+     */
+    @Test
+    @SmallTest
+    public void exitEmergencyCallbackMode_NewEmergencyCallDialedAndCompletes() {
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phone
+        GsmCdmaPhone testPhone = (GsmCdmaPhone) setupTestPhoneForEmergencyCall(
+                /* isRoaming= */ true, /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(testPhone);
+        // Start emergency call then enter ECM
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+        processAllMessages();
+
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.onEmergencyCallDomainUpdated(
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
+        // Set ecm as supported
+        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
+        // End call to enter ECM
+        emergencyStateTracker.endCall(mTestConnection1);
+        processAllMessages();
+
+        // verify ecbm states are correct
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertTrue(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+
+        // 2nd call while in emergency callback mode
+        unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
+        processAllMessages();
+        processAllFutureMessages();
+
+        // verify ecbm states are not changed.
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertTrue(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+
+        // Verify exitEmergencyMode() is not called.
+        verify(testPhone, never()).exitEmergencyMode(any(Message.class));
+
+        // Verify ECBM timer cancel.
+        verify(testPhone).notifyEcbmTimerReset(eq(Boolean.TRUE));
+
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.onEmergencyCallDomainUpdated(
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
+        // End call to enter ECM
+        emergencyStateTracker.endCall(mTestConnection1);
+        processAllMessages();
+
+        // verify ecbm states are correct
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertTrue(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+
+        // Verify ECBM timer reset.
+        verify(testPhone).notifyEcbmTimerReset(eq(Boolean.FALSE));
+
+        // Verify exitEmergencyMode() is not called.
+        verify(testPhone, never()).exitEmergencyMode(any(Message.class));
+
+        processAllFutureMessages();
+
+        // Ensure ECBM states are all correctly false after we exit.
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+
+        // Verify exitEmergencyMode() is called.
+        verify(testPhone).exitEmergencyMode(any(Message.class));
+    }
+
+    /**
+     * Test that new emergency call is dialed while in emergency callback mode and it fails.
+     */
+    @Test
+    @SmallTest
+    public void exitEmergencyCallbackMode_NewEmergencyCallDialedAndFails() {
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phone
+        GsmCdmaPhone testPhone = (GsmCdmaPhone) setupTestPhoneForEmergencyCall(
+                /* isRoaming= */ true, /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(testPhone);
+        // Start emergency call then enter ECM
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+        processAllMessages();
+
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.onEmergencyCallDomainUpdated(
+                PhoneConstants.PHONE_TYPE_IMS, mTestConnection1);
+        // Set ecm as supported
+        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
+        // End call to enter ECM
+        emergencyStateTracker.endCall(mTestConnection1);
+        processAllMessages();
+
+        // verify ecbm states are correct
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertTrue(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+
+        // 2nd call while in emergency callback mode
+        unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
+        processAllMessages();
+        processAllFutureMessages();
+
+        // verify ecbm states are not changed.
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertTrue(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+
+        // Verify exitEmergencyMode() is not called.
+        verify(testPhone, never()).exitEmergencyMode(any(Message.class));
+
+        // Verify ECBM timer cancel.
+        verify(testPhone).notifyEcbmTimerReset(eq(Boolean.TRUE));
+
+        // End call to return to ECM
+        emergencyStateTracker.endCall(mTestConnection1);
+        processAllMessages();
+
+        // verify ecbm states are correct
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertTrue(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+
+        // Verify ECBM timer reset.
+        verify(testPhone).notifyEcbmTimerReset(eq(Boolean.FALSE));
+
+        // Verify exitEmergencyMode() is not called.
+        verify(testPhone, never()).exitEmergencyMode(any(Message.class));
+
+        processAllFutureMessages();
+
+        // Ensure ECBM states are all correctly false after we exit.
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+
+        // Verify exitEmergencyMode() is called.
+        verify(testPhone).exitEmergencyMode(any(Message.class));
+    }
+
     private EmergencyStateTracker setupEmergencyStateTracker(
             boolean isSuplDdsSwitchRequiredForEmergencyCall) {
         doReturn(mPhoneSwitcher).when(mPhoneSwitcherProxy).getPhoneSwitcher();
@@ -1593,7 +2284,8 @@
                 .putString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, esExtensionSec);
     }
 
-    private void setUpAsyncResultForSetEmergencyMode(Phone phone, EmergencyRegResult regResult) {
+    private void setUpAsyncResultForSetEmergencyMode(Phone phone,
+            EmergencyRegistrationResult regResult) {
         doAnswer((invocation) -> {
             Object[] args = invocation.getArguments();
             final Message msg = (Message) args[1];
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
index 2c5a873..5a6fdc2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
@@ -90,7 +90,7 @@
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
 
         verify(mMockPhone).unregisterForServiceStateChanged(any(Handler.class));
-        verify(mSatelliteController).unregisterForSatelliteModemStateChanged(anyInt(), any());
+        verify(mSatelliteController).unregisterForModemStateChanged(anyInt(), any());
         verify(mMockPhone).registerForServiceStateChanged(any(Handler.class),
                 eq(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED), isNull());
         verify(mSatelliteController, never()).registerForSatelliteModemStateChanged(
@@ -110,7 +110,7 @@
 
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
 
-        verify(mSatelliteController).unregisterForSatelliteModemStateChanged(anyInt(), any());
+        verify(mSatelliteController).unregisterForModemStateChanged(anyInt(), any());
         verify(mSatelliteController).registerForSatelliteModemStateChanged(anyInt(), any());
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccCardControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccCardControllerTest.java
index 242c9f8..11d3f14 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccCardControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccCardControllerTest.java
@@ -146,8 +146,8 @@
 
     @Test
     public void testIsEmbeddedSlotActivated() {
-        mEuiccCardController =
-                new EuiccCardController(mContext, null, mEuiccController, mUiccController);
+        mEuiccCardController = new EuiccCardController(mContext, null, mEuiccController,
+                mUiccController, mFeatureFlags);
         when(mUiccController.getUiccSlots())
                 .thenReturn(new UiccSlot[] {mActivatedRemovableSlot});
         assertFalse(mEuiccCardController.isEmbeddedSlotActivated());
@@ -173,8 +173,8 @@
 
     @Test
     public void testIsEmbeddedCardPresent() {
-        mEuiccCardController =
-                new EuiccCardController(mContext, null, mEuiccController, mUiccController);
+        mEuiccCardController = new EuiccCardController(mContext, null, mEuiccController,
+                mUiccController, mFeatureFlags);
         when(mUiccController.getUiccSlots())
                 .thenReturn(new UiccSlot[] {mActivatedRemovableSlot});
         assertFalse(mEuiccCardController.isEmbeddedCardPresent());
@@ -204,8 +204,8 @@
         mSp.edit().remove(KEY_LAST_BOOT_COUNT);
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0);
         when(mUiccController.getUiccSlots()).thenReturn(new UiccSlot[] {mActivatedEsimSlot});
-        mEuiccCardController =
-                new EuiccCardController(mContext, null, mEuiccController, mUiccController);
+        mEuiccCardController = new EuiccCardController(mContext, null, mEuiccController,
+                mUiccController, mFeatureFlags);
 
         mContext.sendBroadcast(new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED));
         processAllMessages();
@@ -218,8 +218,8 @@
         mSp.edit().remove(KEY_LAST_BOOT_COUNT);
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0);
         when(mUiccController.getUiccSlots()).thenReturn(new UiccSlot[] {mNotPresentEsimSlot});
-        mEuiccCardController =
-            new EuiccCardController(mContext, null, mEuiccController, mUiccController);
+        mEuiccCardController = new EuiccCardController(mContext, null, mEuiccController,
+                mUiccController, mFeatureFlags);
 
         mContext.sendBroadcast(new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED));
         processAllMessages();
@@ -232,8 +232,8 @@
         mSp.edit().putInt(KEY_LAST_BOOT_COUNT, 1).apply();
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 1);
         when(mUiccController.getUiccSlots()).thenReturn(new UiccSlot[] {mActivatedEsimSlot});
-        mEuiccCardController =
-                new EuiccCardController(mContext, null, mEuiccController, mUiccController);
+        mEuiccCardController = new EuiccCardController(mContext, null, mEuiccController,
+                mUiccController, mFeatureFlags);
 
         mContext.sendBroadcast(new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED));
         processAllMessages();
@@ -247,8 +247,8 @@
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0);
         when(mUiccController.getUiccSlots())
                 .thenReturn(new UiccSlot[] {mActivatedRemovableSlot, mInactivatedEsimSlot});
-        mEuiccCardController =
-                new EuiccCardController(mContext, null, mEuiccController, mUiccController);
+        mEuiccCardController = new EuiccCardController(mContext, null, mEuiccController,
+                mUiccController, mFeatureFlags);
 
         mContext.sendBroadcast(new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED));
         processAllMessages();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
index d4850c8..ca4576f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
@@ -40,6 +40,7 @@
 import android.os.test.TestLooper;
 import android.service.euicc.EuiccService;
 import android.service.euicc.IEuiccService;
+import android.service.euicc.IGetAvailableMemoryInBytesCallback;
 import android.service.euicc.IGetEidCallback;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -70,6 +71,7 @@
 
     private static final int CARD_ID = 15;
     private static final int PORT_INDEX = 0;
+    private static final long AVAILABLE_MEMORY = 123L;
 
     @Before
     public void setUp() throws Exception {
@@ -136,6 +138,31 @@
     }
 
     @Test
+    public void testInitialState_forAvailableMemory_commandRejected() {
+        prepareEuiccApp(
+                false /* hasPermission */,
+                false /* requiresBindPermission */,
+                false /* hasPriority */);
+        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
+        final AtomicBoolean called = new AtomicBoolean(false);
+        mConnector.getAvailableMemoryInBytes(
+                CARD_ID,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        fail("Command should have failed");
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        assertTrue("Callback called twice", called.compareAndSet(false, true));
+                    }
+                });
+        mLooper.dispatchAll();
+        assertTrue(called.get());
+    }
+
+    @Test
     public void testInitialState_switchCommandRejected() {
         prepareEuiccApp(false /* hasPermission */, false /* requiresBindPermission */,
                 false /* hasPriority */);
@@ -236,6 +263,47 @@
     }
 
     @Test
+    public void testCommandDispatch_forAvailableMemory_success() throws Exception {
+        prepareEuiccApp(
+                true /* hasPermission */,
+                true /* requiresBindPermission */,
+                true /* hasPriority */);
+        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
+        doAnswer(
+                new Answer<Void>() {
+                    @Override
+                    public Void answer(InvocationOnMock invocation) throws Exception {
+                        IGetAvailableMemoryInBytesCallback callback =
+                                invocation.getArgument(1);
+                        callback.onSuccess(AVAILABLE_MEMORY);
+                        return null;
+                    }
+                })
+                .when(mEuiccService)
+                .getAvailableMemoryInBytes(
+                        anyInt(), Mockito.<IGetAvailableMemoryInBytesCallback>any());
+        final AtomicReference<Long> availableMemoryInBytesRef = new AtomicReference<>();
+        mConnector.getAvailableMemoryInBytes(
+                CARD_ID,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        if (availableMemoryInBytesRef.get() != null) {
+                            fail("Callback called twice");
+                        }
+                        availableMemoryInBytesRef.set(availableMemoryInBytes);
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        fail("Command should have succeeded");
+                    }
+                });
+        mLooper.dispatchAll();
+        assertEquals(AVAILABLE_MEMORY, availableMemoryInBytesRef.get().longValue());
+    }
+
+    @Test
     public void testCommandDispatch_remoteException() throws Exception {
         prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
                 true /* hasPriority */);
@@ -259,6 +327,35 @@
     }
 
     @Test
+    public void testCommandDispatch_forAvailableMemory_remoteException() throws Exception {
+        prepareEuiccApp(
+                true /* hasPermission */,
+                true /* requiresBindPermission */,
+                true /* hasPriority */);
+        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
+        doThrow(new RemoteException("failure"))
+                .when(mEuiccService)
+                .getAvailableMemoryInBytes(
+                        anyInt(), Mockito.<IGetAvailableMemoryInBytesCallback>any());
+        final AtomicBoolean called = new AtomicBoolean(false);
+        mConnector.getAvailableMemoryInBytes(
+                CARD_ID,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        fail("Command should have failed");
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        assertTrue("Callback called twice", called.compareAndSet(false, true));
+                    }
+                });
+        mLooper.dispatchAll();
+        assertTrue(called.get());
+    }
+
+    @Test
     public void testCommandDispatch_processDied() throws Exception {
         // Kick off the asynchronous command.
         prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
@@ -288,6 +385,39 @@
     }
 
     @Test
+    public void testCommandDispatch_forAvailableMemory_processDied() throws Exception {
+        // Kick off the asynchronous command.
+        prepareEuiccApp(
+                true /* hasPermission */,
+                true /* requiresBindPermission */,
+                true /* hasPriority */);
+        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
+        final AtomicBoolean called = new AtomicBoolean(false);
+        mConnector.getAvailableMemoryInBytes(
+                CARD_ID,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        fail("Unexpected command success callback");
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        assertTrue("Callback called twice", called.compareAndSet(false, true));
+                    }
+                });
+        mLooper.dispatchAll();
+        assertFalse(called.get());
+
+        // Now, pretend the remote process died.
+        mConnector.onServiceDisconnected(null /* name */);
+        mLooper.dispatchAll();
+
+        // Callback should have been called.
+        assertTrue(called.get());
+    }
+
+    @Test
     public void testLinger() throws Exception {
         prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
                 true /* hasPriority */);
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 fc8dfbf..d5ce447 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -40,6 +41,7 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
+import android.app.admin.flags.Flags;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Context;
 import android.content.Intent;
@@ -48,6 +50,8 @@
 import android.content.pm.Signature;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.euicc.DownloadSubscriptionResult;
 import android.service.euicc.EuiccService;
@@ -70,6 +74,7 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.euicc.EuiccConnector.GetOtaStatusCommandCallback;
 import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.UiccSlot;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
@@ -92,11 +97,15 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class EuiccControllerTest extends TelephonyTest {
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final DownloadableSubscription SUBSCRIPTION =
             DownloadableSubscription.forActivationCode("abcde");
 
@@ -132,6 +141,7 @@
     private static final String ICC_ID = "54321";
     private static final int CARD_ID = 25;
     private static final int REMOVABLE_CARD_ID = 26;
+    private static final long AVAILABLE_MEMORY = 123L;
 
     // Mocked classes
     private EuiccConnector mMockConnector;
@@ -156,8 +166,8 @@
         // Number of OTA status changed.
         private int mNumOtaStatusChanged;
 
-        TestEuiccController(Context context, EuiccConnector connector) {
-            super(context, connector);
+        TestEuiccController(Context context, EuiccConnector connector, FeatureFlags featureFlags) {
+            super(context, connector, featureFlags);
             mNumOtaStatusChanged = 0;
         }
 
@@ -186,6 +196,19 @@
         }
 
         @Override
+        public void refreshSubscriptionsAndSendResult(
+                PendingIntent callbackIntent,
+                int resultCode,
+                Intent extrasIntent,
+                boolean isCallerAdmin,
+                String callingPackage,
+                int cardId,
+                Set<Integer> subscriptions) {
+            mCalledRefreshSubscriptionsAndSendResult = true;
+            sendResult(callbackIntent, resultCode, extrasIntent);
+        }
+
+        @Override
         public void sendOtaStatusChangedBroadcast() {
             ++mNumOtaStatusChanged;
         }
@@ -196,7 +219,7 @@
         super.setUp(getClass().getSimpleName());
         mMockConnector = Mockito.mock(EuiccConnector.class);
         mUiccSlot = Mockito.mock(UiccSlot.class);
-        mController = new TestEuiccController(mContext, mMockConnector);
+        mController = new TestEuiccController(mContext, mMockConnector, mFeatureFlags);
 
         PackageInfo pi = new PackageInfo();
         pi.packageName = PACKAGE_NAME;
@@ -255,6 +278,73 @@
     }
 
     @Test(expected = SecurityException.class)
+    public void testGetAvailableMemoryInBytes_noPrivileges() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID);
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_withPhoneState() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                true /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_withPhoneStatePrivileged() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                true /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_withCarrierPrivileges() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                true /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_failure() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                true /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        assertEquals(
+                EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE,
+                callGetAvailableMemoryInBytes(false /* success */, AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_unsupportedCardId() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                true /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(
+                        true /* success */,
+                        AVAILABLE_MEMORY,
+                        TelephonyManager.UNSUPPORTED_CARD_ID));
+    }
+
+    @Test(expected = SecurityException.class)
     public void testGetOtaStatus_noPrivileges() {
         setHasWriteEmbeddedPermission(false /* hasPermission */);
         callGetOtaStatus(true /* success */, 1 /* status */);
@@ -746,6 +836,132 @@
     }
 
     @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_noAdminPermission()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        setUpUiccSlotData();
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(
+                        EuiccService.RESULT_OK, SUBSCRIPTION_WITH_METADATA);
+        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, false /* switchAfterDownload */, true /* complete */,
+                12345, 0 /* resolvableError */, PACKAGE_NAME /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_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_adminPermission()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        setHasWriteEmbeddedPermission(false);
+
+        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
+                EuiccService.RESULT_OK, 0 /* resolvableError */, "whatever" /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
+        assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_adminPermission_usingSwitchAfterDownload()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        setUpUiccSlotData();
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(
+                        EuiccService.RESULT_OK, SUBSCRIPTION_WITH_METADATA);
+        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_RESOLVABLE_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()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        setHasWriteEmbeddedPermission(true);
+        doReturn(true)
+                .when(mUserManager)
+                .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY);
+
+        assertThrows(SecurityException.class,
+                () ->
+                        callDownloadSubscription(
+                                SUBSCRIPTION,
+                                false /* switchAfterDownload */,
+                                true /* complete */,
+                                EuiccService.RESULT_OK,
+                                0 /* resolvableError */,
+                                "whatever" /* callingPackage */));
+        assertFalse(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_onlyAdminManagedAllowed_callerNotAdmin_disabled_success()
+            throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        setHasWriteEmbeddedPermission(true);
+        doReturn(true)
+                .when(mUserManager)
+                .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY);
+
+        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
+                EuiccService.RESULT_OK, 0 /* resolvableError */, "whatever" /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
+        assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_onlyAdminManagedAllowed_callerIsAdmin_success()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        setHasWriteEmbeddedPermission(false);
+        doReturn(true)
+                .when(mUserManager)
+                .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY);
+
+        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
+                EuiccService.RESULT_OK, 0 /* resolvableError */, "whatever" /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
+        assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
     public void testDeleteSubscription_noSuchSubscription() throws Exception {
         setHasWriteEmbeddedPermission(true);
         callDeleteSubscription(
@@ -789,6 +1005,82 @@
         assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
     }
 
+
+    @Test
+    public void testDeleteSubscription_adminOwned_success() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        String callingPackage = "whatever";
+        SubscriptionInfo subInfo1 = new SubscriptionInfo.Builder()
+                .setId(SUBSCRIPTION_ID)
+                .setEmbedded(true)
+                .setIccId(ICC_ID)
+                .setCardId(CARD_ID)
+                .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX)
+                .setGroupOwner(callingPackage)
+                .build();
+        ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1));
+        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(subInfos);
+
+        callDeleteSubscription(
+                SUBSCRIPTION_ID, ICC_ID, true /* complete */,
+                0 /* result */, callingPackage /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK,
+                0 /* detailedCode */);
+    }
+
+    @Test
+    public void testDeleteSubscription_adminOwned_featureDisabled_success() throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(true);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        String callingPackage = "whatever";
+        SubscriptionInfo subInfo1 = new SubscriptionInfo.Builder()
+                .setId(SUBSCRIPTION_ID)
+                .setEmbedded(true)
+                .setIccId(ICC_ID)
+                .setCardId(CARD_ID)
+                .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX)
+                .setGroupOwner(callingPackage)
+                .build();
+        ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1));
+        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(subInfos);
+
+        callDeleteSubscription(
+                SUBSCRIPTION_ID, ICC_ID, true /* complete */,
+                0 /* result */, callingPackage /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK,
+                0 /* detailedCode */);
+    }
+
+    @Test
+    public void testDeleteSubscription_adminOwned_noPermissions_error() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
+        String callingPackage = "whatever";
+        SubscriptionInfo subInfo1 = new SubscriptionInfo.Builder()
+                .setId(SUBSCRIPTION_ID)
+                .setEmbedded(true)
+                .setIccId(ICC_ID)
+                .setCardId(CARD_ID)
+                .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX)
+                .setGroupOwner(callingPackage)
+                .build();
+        ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1));
+        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(subInfos);
+
+        callDeleteSubscription(
+                SUBSCRIPTION_ID, ICC_ID, true /* complete */,
+                0 /* result */, callingPackage /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR,
+                0 /* detailedCode */);
+    }
+
     @Test
     public void testGetResolvedPortIndexForSubscriptionSwitchWithOutMEP() throws Exception {
         setUpUiccSlotData();
@@ -1295,6 +1587,30 @@
     }
 
     @Test
+    @EnableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK,
+            TelephonyManager.ENABLE_FEATURE_MAPPING})
+    public void testIsSimPortAvailable_WithTelephonyFeatureMapping() {
+        // Feature flag enabled, device has required telephony feature.
+        doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+        doReturn(true).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_EUICC));
+
+        setUiccCardInfos(false, true, true);
+
+        // assert non euicc card id
+        assertFalse(mController.isSimPortAvailable(REMOVABLE_CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // assert invalid port index
+        assertFalse(mController.isSimPortAvailable(CARD_ID, 5 /* portIndex */, TEST_PACKAGE_NAME));
+
+        // Device does not have required telephony feature.
+        doReturn(false).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_EUICC));
+        assertThrows(UnsupportedOperationException.class,
+                () -> mController.isSimPortAvailable(REMOVABLE_CARD_ID, 0, TEST_PACKAGE_NAME));
+    }
+
+    @Test
     @EnableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK})
     public void testIsSimPortAvailable_invalidCase() {
         setUiccCardInfos(false, true, true);
@@ -1469,6 +1785,25 @@
         setHasCarrierPrivilegesOnActiveSubscription(hasCarrierPrivileges);
     }
 
+    private void setGetAvailableMemoryInBytesPermissions(
+            boolean hasPhoneState, boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges)
+            throws Exception {
+        doReturn(
+                        hasPhoneState
+                                ? PackageManager.PERMISSION_GRANTED
+                                : PackageManager.PERMISSION_DENIED)
+                .when(mContext)
+                .checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        doReturn(
+                        hasPhoneStatePrivileged
+                                ? PackageManager.PERMISSION_GRANTED
+                                : PackageManager.PERMISSION_DENIED)
+                .when(mContext)
+                .checkCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        when(mTelephonyManager.getPhoneCount()).thenReturn(1);
+        setHasCarrierPrivilegesOnActiveSubscription(hasCarrierPrivileges);
+    }
+
     private void setHasWriteEmbeddedPermission(boolean hasPermission) {
         doReturn(hasPermission
                 ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED)
@@ -1476,6 +1811,15 @@
                 .checkCallingOrSelfPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS);
     }
 
+    private void setHasManageDevicePolicyManagedSubscriptionsPermission(boolean hasPermission) {
+        doReturn(hasPermission
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED)
+                .when(mContext)
+                .checkCallingOrSelfPermission(
+                        Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS);
+    }
+
+
     private void setHasMasterClearPermission(boolean hasPermission) {
         Stubber stubber = hasPermission ? doNothing() : doThrow(new SecurityException());
         stubber.when(mContext).enforceCallingPermission(
@@ -1592,6 +1936,29 @@
         return mController.getEid(cardId, PACKAGE_NAME);
     }
 
+    private long callGetAvailableMemoryInBytes(
+            final boolean success, final long availableMemoryInBytes, int cardId) {
+        doAnswer(
+                new Answer<Void>() {
+                    @Override
+                    public Void answer(InvocationOnMock invocation) throws Exception {
+                        EuiccConnector.GetAvailableMemoryInBytesCommandCallback cb =
+                                invocation.getArgument(1 /* resultCallback */);
+                        if (success) {
+                            cb.onGetAvailableMemoryInBytesComplete(availableMemoryInBytes);
+                        } else {
+                            cb.onEuiccServiceUnavailable();
+                        }
+                        return null;
+                    }
+                })
+                .when(mMockConnector)
+                .getAvailableMemoryInBytes(
+                        anyInt(),
+                        Mockito.<EuiccConnector.GetAvailableMemoryInBytesCommandCallback>any());
+        return mController.getAvailableMemoryInBytes(cardId, PACKAGE_NAME);
+    }
+
     private int callGetOtaStatus(final boolean success, final int status) {
         doAnswer(new Answer<Void>() {
             @Override
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 1c1ca0f..17a428b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
@@ -36,11 +36,14 @@
 import com.android.internal.telephony.GsmCdmaPhone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
+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.Mock;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
@@ -56,15 +59,18 @@
     private static final String TEST_DIAL_STRING_NON_EMERGENCY_NUMBER = "11976";
     private GsmMmiCode mGsmMmiCode;
     private GsmCdmaPhone mGsmCdmaPhoneUT;
+    @Mock private FeatureFlags mFeatureFlags;
 
     private final Executor mExecutor = Runnable::run;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
         doReturn(mExecutor).when(mContext).getMainExecutor();
         mGsmCdmaPhoneUT = new GsmCdmaPhone(mContext, mSimulatedCommands, mNotifier, true, 0,
-                PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager);
+                PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager,
+                mFeatureFlags);
         setCarrierSupportsCallerIdVerticalServiceCodesCarrierConfig();
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimServiceTableTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimServiceTableTest.java
index a2965c6..f128f65 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimServiceTableTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimServiceTableTest.java
@@ -17,7 +17,8 @@
 package com.android.internal.telephony.gsm;
 
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.uicc.UsimServiceTable;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsCallProfileTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsCallProfileTest.java
index 48148ba..66a18b0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsCallProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsCallProfileTest.java
@@ -27,8 +27,8 @@
 import android.os.Parcelable;
 import android.telecom.DisconnectCause;
 import android.telephony.ims.ImsCallProfile;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java
index c6f45d9..270960c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java
@@ -33,8 +33,8 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.telephony.ims.aidl.IImsServiceController;
-import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
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 abc231a..7e65048 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -52,13 +52,14 @@
 import android.telephony.ims.ImsService;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseIntArray;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.ims.ImsFeatureBinderRepository;
 import com.android.internal.telephony.PhoneConfigurationManager;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
index 4a3ceaa..2544fc1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
@@ -38,9 +38,9 @@
 import android.os.RemoteException;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.SparseIntArray;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.ims.ImsFeatureBinderRepository;
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 adfc4a3..0b6aa9f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
@@ -48,9 +48,9 @@
 import android.telephony.ims.aidl.IImsServiceController;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.SparseIntArray;
 
+import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.ims.ImsFeatureBinderRepository;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
index aa07125..47e780e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.imsphone;
 
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -35,7 +36,8 @@
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsStreamMediaProfile;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.ims.ImsCall;
 import com.android.internal.telephony.TelephonyTest;
@@ -237,4 +239,40 @@
         assertFalse(mTestImsCall.isWifiCall());
         assertEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
     }
+
+    @Test
+    @SmallTest
+    public void testListenerCalledAfterCallClosed() throws Exception {
+        ImsCallSession mockSession = mock(ImsCallSession.class);
+        ImsCall testImsCall = new ImsCall(mContext, mTestCallProfile);
+        ImsCallProfile profile = new ImsCallProfile();
+        when(mockSession.getCallProfile()).thenReturn(profile);
+        testImsCall.attachSession(mockSession);
+
+        ArgumentCaptor<ImsCallSession.Listener> listenerCaptor =
+                ArgumentCaptor.forClass(ImsCallSession.Listener.class);
+        verify(mockSession).setListener(listenerCaptor.capture(), any());
+        ImsCallSession.Listener listener = listenerCaptor.getValue();
+        assertNotNull(listener);
+
+        // Call closed
+        testImsCall.close();
+        // Set CallProfile value to null because ImsCallSession was closed
+        when(mockSession.getCallProfile()).thenReturn(null);
+
+        // Set new profile with direction of none
+        ImsStreamMediaProfile newProfile = new ImsStreamMediaProfile(
+                ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB,
+                ImsStreamMediaProfile.DIRECTION_INACTIVE,
+                ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                ImsStreamMediaProfile.DIRECTION_INACTIVE,
+                ImsStreamMediaProfile.RTT_MODE_DISABLED);
+        try {
+            listener.callSessionProgressing(mockSession, newProfile);
+        } catch (Exception e) {
+            throw new AssertionError("not expected exception", e);
+        }
+
+        assertNull(testImsCall.getCallProfile());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java
index 7d6557d..3b362b1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java
@@ -41,6 +41,9 @@
 import android.util.ArraySet;
 
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
@@ -66,6 +69,8 @@
     private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
     private Handler mPreciseCallStateHandler;
 
+    private Phone mPassthroughPhone;
+
     @Mock private ImsPhoneCall mForegroundCall;
     @Mock private ImsPhoneCall mBackgroundCall;
     private Call.State mActiveState = ImsPhoneCall.State.ACTIVE;
@@ -90,7 +95,13 @@
         doReturn(mAnyInt).when(mImsPhone).getSubId();
         doReturn(mContextFixture.getCarrierConfigBundle()).when(mCarrierConfigManager)
                 .getConfigForSubId(anyInt(), any());
-        doReturn(mPhone).when(mImsPhone).getDefaultPhone();
+
+        mPassthroughPhone = new GsmCdmaPhone(
+                mContext, mSimulatedCommands, mNotifier, true, 0,
+                PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory,
+                (c, p) -> mImsManager, mFeatureFlags);
+
+        doReturn(mPassthroughPhone).when(mImsPhone).getDefaultPhone();
 
         doReturn(mForegroundCall).when(mImsPhone).getForegroundCall();
         doReturn(mBackgroundCall).when(mImsPhone).getBackgroundCall();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
index c4bb864..f0ae0f1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
@@ -31,9 +31,9 @@
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsStreamMediaProfile;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.ims.ImsCall;
 import com.android.ims.ImsException;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
index d0a2094..79b1ada 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -94,6 +94,7 @@
 import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
+import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RtpHeaderExtensionType;
 import android.telephony.ims.SrvccCall;
 import android.telephony.ims.aidl.IImsTrafficSessionCallback;
@@ -101,12 +102,12 @@
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.ims.FeatureConnector;
 import com.android.ims.ImsCall;
@@ -125,7 +126,6 @@
 import com.android.internal.telephony.SrvccConnection;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.d2d.RtpTransport;
-import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker.VtDataUsageProvider;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 
@@ -160,6 +160,7 @@
     private Bundle mBundle = new Bundle();
     private static final int SUB_0 = 0;
     @Nullable private VtDataUsageProvider mVtDataUsageProvider;
+    private ProvisioningManager.Callback mConfigCallback;
 
     // Mocked classes
     private ArgumentCaptor<Set<RtpHeaderExtensionType>> mRtpHeaderExtensionTypeCaptor;
@@ -172,7 +173,6 @@
     private INetworkStatsProviderCallback mVtDataUsageProviderCb;
     private ImsPhoneCallTracker.ConnectorFactory mConnectorFactory;
     private CommandsInterface mMockCi;
-    private DomainSelectionResolver mDomainSelectionResolver;
     private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
 
     private final Executor mExecutor = Runnable::run;
@@ -240,7 +240,6 @@
         doReturn(ImsFeature.STATE_READY).when(mImsManager).getImsServiceState();
         doReturn(mImsCallProfile).when(mImsManager).createCallProfile(anyInt(), anyInt());
         mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
-        mDomainSelectionResolver = mock(DomainSelectionResolver.class);
 
         doReturn(new SubscriptionInfoInternal.Builder().setSimSlotIndex(0).setId(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
@@ -279,13 +278,14 @@
             return mMockConnector;
         }).when(mConnectorFactory).create(any(), anyInt(), anyString(), any(), any());
 
-        DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
-        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        doReturn(false)
+                .when(mFeatureFlags).updateImsServiceByGatheringProvisioningChanges();
 
         // Capture CarrierConfigChangeListener to emulate the carrier config change notification
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        mCTUT = new ImsPhoneCallTracker(mImsPhone, mConnectorFactory, Runnable::run);
+        mCTUT = new ImsPhoneCallTracker(mImsPhone, mConnectorFactory, Runnable::run,
+                mFeatureFlags);
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
         mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
@@ -309,6 +309,12 @@
 
         verify(mMockConnector).connect();
         mConnectorListener.connectionReady(mImsManager, SUB_0);
+
+        final ArgumentCaptor<ProvisioningManager.Callback> configCallbackCaptor =
+                ArgumentCaptor.forClass(ProvisioningManager.Callback.class);
+        verify(mImsConfig).addConfigCallback(configCallbackCaptor.capture());
+        mConfigCallback = configCallbackCaptor.getValue();
+        assertNotNull(mConfigCallback);
     }
 
     @After
@@ -2641,6 +2647,73 @@
                 eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
     }
 
+    @Test
+    public void testProvisioningItemAndUpdateImsServiceConfigWithFeatureEnabled() {
+        doReturn(true)
+                .when(mFeatureFlags).updateImsServiceByGatheringProvisioningChanges();
+
+        // Receive a subscription loaded and IMS connection ready indication.
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        sendCarrierConfigChanged();
+        processAllMessages();
+        verify(mImsManager, times(1)).updateImsServiceConfig();
+
+        logd("deliver provisioning items");
+        mConfigCallback.onProvisioningIntChanged(27, 2);
+        mConfigCallback.onProvisioningIntChanged(28, 1);
+        mConfigCallback.onProvisioningIntChanged(10, 1);
+        mConfigCallback.onProvisioningIntChanged(11, 1);
+        mConfigCallback.onProvisioningStringChanged(12, "msg.pc.t-mobile.com");
+        mConfigCallback.onProvisioningIntChanged(26, 0);
+        mConfigCallback.onProvisioningIntChanged(66, 0);
+
+        logd("proc provisioning items");
+        processAllFutureMessages();
+
+        // updateImsServiceConfig is called with below 2 events.
+        // 1. CarrierConfig
+        // 2. ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE(28), ProvisioningManager
+        // .KEY_VOLTE_PROVISIONING_STATUS(10) and ProvisioningManager.KEY_VT_PROVISIONING_STATUS(11)
+        verify(mImsManager, times(2)).updateImsServiceConfig();
+    }
+
+
+    @Test
+    public void testProvisioningItemAndUpdateImsServiceConfigWithFeatureDisabled() {
+        doReturn(false)
+                .when(mFeatureFlags).updateImsServiceByGatheringProvisioningChanges();
+
+        // Receive a subscription loaded and IMS connection ready indication.
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        sendCarrierConfigChanged();
+        processAllMessages();
+        verify(mImsManager, times(1)).updateImsServiceConfig();
+
+        logd("deliver provisioning items");
+        mConfigCallback.onProvisioningIntChanged(27, 2);
+        //ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE(28) call updateImsServiceConfig.
+        mConfigCallback.onProvisioningIntChanged(28, 1);
+        //ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS(10) call updateImsServiceConfig.
+        mConfigCallback.onProvisioningIntChanged(10, 1);
+        //ProvisioningManager.KEY_VT_PROVISIONING_STATUS(11) call updateImsServiceConfig.
+        mConfigCallback.onProvisioningIntChanged(11, 1);
+        mConfigCallback.onProvisioningStringChanged(12, "msg.pc.t-mobile.com");
+        mConfigCallback.onProvisioningIntChanged(26, 0);
+        mConfigCallback.onProvisioningIntChanged(66, 0);
+
+        logd("proc provisioning items");
+        processAllFutureMessages();
+
+        // updateImsServiceConfig is called with below 4 events.
+        // 1. CarrierConfig
+        // 2. ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE(28)
+        // 3. ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS(10)
+        // 4. ProvisioningManager.KEY_VT_PROVISIONING_STATUS(11)
+        verify(mImsManager, times(4)).updateImsServiceConfig();
+    }
+
     private void sendCarrierConfigChanged() {
         mCarrierConfigChangeListener.onCarrierConfigChanged(mPhone.getPhoneId(), mPhone.getSubId(),
                 TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
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 396466c..6c4493b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
@@ -46,11 +46,12 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsStreamMediaProfile;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
 import com.android.ims.ImsCall;
 import com.android.ims.ImsException;
 import com.android.internal.telephony.Call;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneFactoryTest.java
index a657ba2..6937469 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneFactoryTest.java
@@ -24,10 +24,12 @@
 import static org.mockito.Mockito.verify;
 
 import android.os.HandlerThread;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.PhoneNotifier;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -42,6 +44,8 @@
     private ImsPhone mImsPhoneUT;
     private ImsPhoneFactoryHandler mImsPhoneFactoryHandler;
 
+    private FeatureFlags mFeatureFlags;
+
     private final Executor mExecutor = Runnable::run;
 
     private class ImsPhoneFactoryHandler extends HandlerThread {
@@ -51,7 +55,8 @@
         }
         @Override
         public void onLooperPrepared() {
-            mImsPhoneUT = ImsPhoneFactory.makePhone(mContext, mPhoneNotifier, mPhone);
+            mImsPhoneUT = ImsPhoneFactory.makePhone(mContext, mPhoneNotifier, mPhone,
+                    mFeatureFlags);
             setReady(true);
         }
     }
@@ -60,6 +65,7 @@
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mPhoneNotifier = mock(PhoneNotifier.class);
+        mFeatureFlags = mock(FeatureFlags.class);
         doReturn(mExecutor).when(mContext).getMainExecutor();
 
         mImsPhoneFactoryHandler = new ImsPhoneFactoryHandler(getClass().getSimpleName());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
index d8173a2..3c1b045 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
@@ -29,12 +29,14 @@
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -57,6 +59,7 @@
 
     // Mocked classes
     private ServiceState mServiceState;
+    private FeatureFlags mFeatureFlags;
 
     private final Executor mExecutor = Runnable::run;
 
@@ -64,12 +67,14 @@
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mServiceState = mock(ServiceState.class);
+        mFeatureFlags = mock(FeatureFlags.class);
+
         doReturn(mExecutor).when(mContext).getMainExecutor();
         doReturn(mPhone).when(mPhone).getDefaultPhone();
         doReturn(mServiceState).when(mPhone).getServiceState();
         doReturn(false).when(mServiceState).getVoiceRoaming();
         doReturn(false).when(mPhone).supportsConversionOfCdmaCallerIdMmiCodesWhileRoaming();
-        mImsPhoneUT = new ImsPhone(mContext, mNotifier, mPhone);
+        mImsPhoneUT = new ImsPhone(mContext, mNotifier, mPhone, mFeatureFlags);
         setCarrierSupportsCallerIdVerticalServiceCodesCarrierConfig();
     }
 
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 cd9d9ad..14cff4b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
@@ -22,8 +22,10 @@
 import static android.telephony.CarrierConfigManager.USSD_OVER_IMS_ONLY;
 import static android.telephony.CarrierConfigManager.USSD_OVER_IMS_PREFERRED;
 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;
 import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT;
+import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_RAT_BLOCK;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_3G;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
@@ -61,6 +63,7 @@
 
 import android.app.Activity;
 import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.net.Uri;
@@ -80,11 +83,11 @@
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsUtImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.ims.ImsEcbmStateListener;
 import com.android.ims.ImsUtInterface;
@@ -97,6 +100,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsPhone.SS;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -122,9 +126,9 @@
     private ImsPhoneCall mBackgroundCall;
     private ImsPhoneCall mRingingCall;
     private Handler mTestHandler;
-    private DomainSelectionResolver mDomainSelectionResolver;
     Connection mConnection;
     ImsUtInterface mImsUtInterface;
+    private FeatureFlags mFeatureFlags;
 
     private final Executor mExecutor = Runnable::run;
 
@@ -148,9 +152,7 @@
         mTestHandler = mock(Handler.class);
         mConnection = mock(Connection.class);
         mImsUtInterface = mock(ImsUtInterface.class);
-        mDomainSelectionResolver = mock(DomainSelectionResolver.class);
-        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
-        DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
+        mFeatureFlags = mock(FeatureFlags.class);
 
         mImsCT.mForegroundCall = mForegroundCall;
         mImsCT.mBackgroundCall = mBackgroundCall;
@@ -162,7 +164,8 @@
 
         doReturn(true).when(mTelephonyManager).isVoiceCapable();
 
-        mImsPhoneUT = new ImsPhone(mContext, mNotifier, mPhone, (c, p) -> mImsManager, true);
+        mImsPhoneUT = new ImsPhone(mContext, mNotifier, mPhone, (c, p) -> mImsManager, true,
+                mFeatureFlags);
 
         mDoesRilSendMultipleCallRing = TelephonyProperties.ril_sends_multiple_call_ring()
                 .orElse(true);
@@ -194,7 +197,6 @@
     public void tearDown() throws Exception {
         mImsPhoneUT = null;
         mBundle = null;
-        DomainSelectionResolver.setDomainSelectionResolver(null);
         super.tearDown();
     }
 
@@ -818,6 +820,37 @@
     }
 
     @Test
+    @SmallTest
+    public void testSetWfcModeInRoaming() throws Exception {
+        doReturn(true).when(mFeatureFlags).updateRoamingStateToSetWfcMode();
+        doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
+        doReturn(true).when(mPhone).isRadioOn();
+
+        // Set CarrierConfigManager to be invalid
+        doReturn(null).when(mContext).getSystemService(Context.CARRIER_CONFIG_SERVICE);
+
+        //Roaming - data registration only on LTE
+        Message m = getServiceStateChangedMessage(getServiceStateDataOnly(
+                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.STATE_IN_SERVICE, true));
+        // Inject the message synchronously instead of waiting for the thread to do it.
+        mImsPhoneUT.handleMessage(m);
+        m.recycle();
+
+        verify(mImsManager, never()).setWfcMode(anyInt(), eq(true));
+
+        // Set CarrierConfigManager to be valid
+        doReturn(mCarrierConfigManager).when(mContext)
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+
+        m = getServiceStateChangedMessage(getServiceStateDataOnly(
+                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.STATE_IN_SERVICE, true));
+        mImsPhoneUT.handleMessage(m);
+        m.recycle();
+
+        verify(mImsManager, times(1)).setWfcMode(anyInt(), eq(true));
+    }
+
+    @Test
     public void testNonNullTrackersInImsPhone() throws Exception {
         assertNotNull(mImsPhoneUT.getEmergencyNumberTracker());
         assertNotNull(mImsPhoneUT.getServiceStateTracker());
@@ -1111,6 +1144,37 @@
         mContextFixture.addCallingOrSelfPermission("");
     }
 
+    @Test
+    @SmallTest
+    public void testClearPhoneNumberForSourceIms() {
+        doReturn(true).when(mFeatureFlags)
+                .clearCachedImsPhoneNumberWhenDeviceLostImsRegistration();
+
+        // In reality the method under test runs in phone process so has MODIFY_PHONE_STATE
+        mContextFixture.addCallingOrSelfPermission(MODIFY_PHONE_STATE);
+        int subId = 1;
+        doReturn(subId).when(mPhone).getSubId();
+        doReturn(new SubscriptionInfoInternal.Builder().setId(subId).setSimSlotIndex(0)
+                .setCountryIso("gb").build()).when(mSubscriptionManagerService)
+                .getSubscriptionInfoInternal(subId);
+
+        // 1. Two valid phone number; 1st is set.
+        Uri[] associatedUris = new Uri[] {
+                Uri.parse("sip:+447539447777@ims.x.com"),
+                Uri.parse("tel:+447539446666")
+        };
+        mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
+
+        verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539447777");
+
+        mImsPhoneUT.clearPhoneNumberForSourceIms();
+
+        verify(mSubscriptionManagerService).setNumberFromIms(subId, "");
+
+        // Clean up
+        mContextFixture.addCallingOrSelfPermission("");
+    }
+
     /**
      * Verifies that valid radio technology is passed to RIL
      * when IMS registration state changes to registered.
@@ -1366,13 +1430,14 @@
 
         assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
 
-        // duplicated notification with the same suggested action
+        // verifies that duplicated notification with the same suggested action is invoked
         registrationCallback.onUnregistered(reasonInfo,
                 SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_LTE);
         regInfo = mSimulatedCommands.getImsRegistrationInfo();
 
-        // verify that there is no update in the SimulatedCommands
-        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK);
 
         // unregistered with repeated error
         registrationCallback.onUnregistered(reasonInfo,
@@ -1390,14 +1455,15 @@
 
         assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
 
-        // duplicated notification with the same suggested action
+        // verfies that duplicated notification with the same suggested action is invoked
         registrationCallback.onUnregistered(reasonInfo,
                 SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT,
                 REGISTRATION_TECH_LTE);
         regInfo = mSimulatedCommands.getImsRegistrationInfo();
 
-        // verify that there is no update in the SimulatedCommands
-        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT);
 
         // unregistered with temporary error
         registrationCallback.onUnregistered(reasonInfo,
@@ -1408,6 +1474,19 @@
                 && regInfo[1] == REGISTRATION_TECH_LTE
                 && regInfo[2] == SUGGESTED_ACTION_NONE);
 
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        // verfies that duplicated notification with temporary error is discarded
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_NONE, REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
         // verifies that reason codes except ImsReasonInfo.CODE_REGISTRATION_ERROR are discarded.
         reasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE,
                 ImsReasonInfo.CODE_UNSPECIFIED, "");
@@ -1425,7 +1504,7 @@
 
         assertTrue(regInfo[0] == 1 && regInfo[1] == 1 && regInfo[2] == 1);
 
-        // duplicated notification with the same suggested action
+        // verifies that duplicated notification with temporary error is discarded
         registrationCallback.onUnregistered(reasonInfo,
                 SUGGESTED_ACTION_NONE, REGISTRATION_TECH_NR);
         regInfo = mSimulatedCommands.getImsRegistrationInfo();
@@ -1434,6 +1513,81 @@
         assertTrue(regInfo[0] == 1 && regInfo[1] == 1 && regInfo[2] == 1);
     }
 
+    /**
+     * Verifies that valid state and reason is passed to RIL with RAT suggested actions
+     * when IMS registration state changes to unregistered.
+     */
+    @Test
+    @SmallTest
+    public void testUpdateImsRegistrationInfoWithRatSuggestedAction() {
+        doReturn(true).when(mFeatureFlags)
+                .addRatRelatedSuggestedActionToImsRegistration();
+
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+
+        int[] regInfo = mSimulatedCommands.getImsRegistrationInfo();
+        assertNotNull(regInfo);
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        RegistrationManager.RegistrationCallback registrationCallback =
+                mImsPhoneUT.getImsMmTelRegistrationCallback();
+
+        ImsReasonInfo reasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR,
+                ImsReasonInfo.CODE_UNSPECIFIED, "");
+
+        // unregistered with rat block
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_RAT_BLOCK,
+                REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_RAT_BLOCK);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        // verfies that duplicated notification with the same suggested action is invoked
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_RAT_BLOCK,
+                REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_RAT_BLOCK);
+
+        // unregistered with rat block clear
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS,
+                REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        // verfies that duplicated notification with the same suggested action is invoked
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS,
+                REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS);
+    }
+
     @Test
     @SmallTest
     public void testImsDialArgsBuilderFromForAlternateService() {
@@ -1449,6 +1603,17 @@
         assertEquals(2, copiedDialArgs.eccCategory);
     }
 
+    @Test
+    @SmallTest
+    public void testCanMakeWifiCall() {
+        mImsPhoneUT.setServiceState(ServiceState.STATE_IN_SERVICE);
+        mImsPhoneUT.setImsRegistered(true);
+        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN).when(mImsCT)
+                .getImsRegistrationTech();
+
+        assertTrue(mImsPhoneUT.canMakeWifiCall());
+    }
+
     private ServiceState getServiceStateDataAndVoice(int rat, int regState, boolean isRoaming) {
         ServiceState ss = new ServiceState();
         ss.setStateOutOfService();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java
index 93fbfdc..451c315 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java
@@ -37,7 +37,8 @@
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.RegistrationManager.RegistrationCallback;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper.ImsRegistrationUpdate;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java
new file mode 100644
index 0000000..d63dc3c
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2023 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.when;
+
+import android.net.NetworkCapabilities;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.telephony.DataFailCause;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+public class DataCallSessionStatsTest extends TelephonyTest {
+
+    private DataCallResponse mDefaultImsResponse = buildDataCallResponse("ims", 0);
+    private static class TestableDataCallSessionStats extends DataCallSessionStats {
+        private long mTimeMillis = 0L;
+
+        TestableDataCallSessionStats(Phone phone) {
+            super(phone);
+        }
+
+        @Override
+        protected long getTimeMillis() {
+            return mTimeMillis;
+        }
+
+        private void setTimeMillis(long timeMillis) {
+            mTimeMillis = timeMillis;
+        }
+    }
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+    private TestableDataCallSessionStats mDataCallSessionStats;
+    private NetworkCapabilities mCellularNetworkCapabilities = new NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        mDataCallSessionStats = new TestableDataCallSessionStats(mPhone);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mDataCallSessionStats = null;
+        super.tearDown();
+    }
+
+    private DataCallResponse buildDataCallResponse(String apn, long retryDurationMillis) {
+        return new DataCallResponse.Builder()
+                .setId(apn.hashCode())
+                .setRetryDurationMillis(retryDurationMillis)
+                .build();
+    }
+
+    @Test
+    @SmallTest
+    public void testSetupDataCallOnCellularIms_success() {
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NONE);
+
+        mDataCallSessionStats.setTimeMillis(60000L);
+        mDataCallSessionStats.conclude();
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+                ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addDataCallSession(
+                callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask);
+        assertEquals(1, stats.durationMinutes);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.ratAtEnd);
+        assertTrue(stats.ongoing);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetupDataCallOnIwlan_success() {
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_IWLAN,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NONE);
+
+        mDataCallSessionStats.setTimeMillis(120000L);
+        mDataCallSessionStats.conclude();
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+                ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addDataCallSession(
+                callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask);
+        assertEquals(2, stats.durationMinutes);
+        assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd);
+        assertFalse(stats.isIwlanCrossSim);
+        assertTrue(stats.ongoing);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetupDataCallOnCrossSimCalling_success() {
+        doReturn(mCellularNetworkCapabilities)
+                .when(mDefaultNetworkMonitor).getNetworkCapabilities();
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_IWLAN,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NONE);
+
+        mDataCallSessionStats.setTimeMillis(60000L);
+        mDataCallSessionStats.conclude();
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+                ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addDataCallSession(
+                callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask);
+        assertEquals(1, stats.durationMinutes);
+        assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd);
+        assertTrue(stats.isIwlanCrossSim);
+        assertTrue(stats.ongoing);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetupDataCallOnCellularIms_failure() {
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NETWORK_FAILURE);
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+                ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addDataCallSession(
+                callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask);
+        assertEquals(0, stats.durationMinutes);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.ratAtEnd);
+        assertFalse(stats.ongoing);
+    }
+
+    @Test
+    @SmallTest
+    public void testHandoverFromCellularToIwlan_success() {
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NONE);
+
+        mDataCallSessionStats.onDrsOrRatChanged(TelephonyManager.NETWORK_TYPE_IWLAN);
+        mDataCallSessionStats.conclude();
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+                ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addDataCallSession(
+                callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask);
+        assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd);
+        assertEquals(1, stats.ratSwitchCount);
+        assertTrue(stats.ongoing);
+    }
+
+    @Test
+    @SmallTest
+    public void testHandoverFromCellularToCrossSimCalling_success() {
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NONE);
+
+        doReturn(mCellularNetworkCapabilities)
+                .when(mDefaultNetworkMonitor).getNetworkCapabilities();
+        mDataCallSessionStats.onDrsOrRatChanged(TelephonyManager.NETWORK_TYPE_IWLAN);
+        mDataCallSessionStats.conclude();
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+                ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addDataCallSession(
+                callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask);
+        assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd);
+        assertEquals(1, stats.ratSwitchCount);
+        assertTrue(stats.isIwlanCrossSim);
+        assertTrue(stats.ongoing);
+    }
+
+    @Test
+    @SmallTest
+    public void testHandoverFromCellularToIwlan_failure() {
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NONE);
+
+        mDataCallSessionStats.onHandoverFailure(DataFailCause.IWLAN_DNS_RESOLUTION_TIMEOUT,
+                TelephonyManager.NETWORK_TYPE_LTE, TelephonyManager.NETWORK_TYPE_IWLAN);
+        mDataCallSessionStats.conclude();
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+                ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addDataCallSession(
+                callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.ratAtEnd);
+        assertTrue(stats.ongoing);
+        assertEquals(DataFailCause.IWLAN_DNS_RESOLUTION_TIMEOUT,
+                stats.handoverFailureCauses[0]);
+
+        int cellularToIwlanFailureDirection = TelephonyManager.NETWORK_TYPE_LTE
+                | (TelephonyManager.NETWORK_TYPE_IWLAN << 16);
+        assertEquals(cellularToIwlanFailureDirection, stats.handoverFailureRat[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetupDataCallOnIwlan_success_thenOOS() {
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_IWLAN,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NONE);
+        when(mServiceState.getDataRegistrationState())
+                .thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        mDataCallSessionStats.onDataCallDisconnected(DataFailCause.IWLAN_IKE_DPD_TIMEOUT);
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+                ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addDataCallSession(
+                callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask);
+        assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd);
+        assertTrue(stats.oosAtEnd);
+        assertFalse(stats.ongoing);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/ImsStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/ImsStatsTest.java
index ec554b3..f831c98 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ImsStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ImsStatsTest.java
@@ -45,7 +45,8 @@
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyTest;
@@ -197,6 +198,7 @@
         assertEquals(2000L, stats.utAvailableMillis);
         assertEquals(2000L, stats.smsCapableMillis);
         assertEquals(2000L, stats.smsAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -291,6 +293,7 @@
         assertEquals(0L, stats.utAvailableMillis);
         assertEquals(0L, stats.smsCapableMillis);
         assertEquals(0L, stats.smsAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -320,6 +323,7 @@
         assertEquals(2000L, stats.registeredMillis);
         assertEquals(2000L, stats.voiceCapableMillis);
         assertEquals(2000L, stats.voiceAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -346,12 +350,192 @@
         mImsStats.onImsCapabilitiesChanged(
                 REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_ALL));
 
+        mImsStats.onImsUnregistered(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 999, "Timeout"));
+
         mImsStats.incTimeMillis(2000L);
         mImsStats.conclude();
 
-        // No atom should be generated
+        ArgumentCaptor<ImsRegistrationTermination> terminationCaptor =
+                ArgumentCaptor.forClass(ImsRegistrationTermination.class);
+        verify(mPersistAtomsStorage).addImsRegistrationTermination(terminationCaptor.capture());
+        ImsRegistrationTermination termination = terminationCaptor.getValue();
+        assertEquals(CARRIER1_ID, termination.carrierId);
+        assertFalse(termination.isMultiSim);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, termination.ratAtEnd);
+        assertTrue(termination.setupFailed);
+        assertEquals(ImsReasonInfo.CODE_REGISTRATION_ERROR, termination.reasonCode);
+        assertEquals(999, termination.extraCode);
+        assertEquals("Timeout", termination.extraMessage);
+
+        // Registering duration should be counted
+        ArgumentCaptor<ImsRegistrationStats> statsCaptor =
+                ArgumentCaptor.forClass(ImsRegistrationStats.class);
+        verify(mPersistAtomsStorage).addImsRegistrationStats(statsCaptor.capture());
+        ImsRegistrationStats stats = statsCaptor.getValue();
+        assertEquals(CARRIER1_ID, stats.carrierId);
+        assertEquals(0, stats.simSlotIndex);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
+        assertEquals(2000L, stats.unregisteredMillis);
+        assertEquals(0, stats.registeredTimes);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    @SmallTest
+    public void conclude_registering() throws Exception {
+        // IMS over LTE
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_VOICE,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_VIDEO,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_UT,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_SMS,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_ALL));
+
+        mImsStats.onImsRegistering(TRANSPORT_TYPE_WLAN);
+
+        mImsStats.incTimeMillis(2000L);
+        mImsStats.conclude();
+
+        // Registering duration should be counted
         ArgumentCaptor<ImsRegistrationStats> captor =
                 ArgumentCaptor.forClass(ImsRegistrationStats.class);
+        verify(mPersistAtomsStorage).addImsRegistrationStats(captor.capture());
+        ImsRegistrationStats stats = captor.getValue();
+        assertEquals(CARRIER1_ID, stats.carrierId);
+        assertEquals(0, stats.simSlotIndex);
+        assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.rat);
+        assertEquals(2000L, stats.registeringMillis);
+        assertEquals(0, stats.registeredTimes);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    @SmallTest
+    public void conclude_serviceStateChanged_afterRatUnknown() throws Exception {
+        // IMS over LTE
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_VOICE,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_VIDEO,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_UT,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_SMS,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_ALL));
+
+        mImsStats.onImsUnregistered(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 999, "Timeout"));
+
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+                .when(mServiceState)
+                .getDataNetworkType();
+        mImsStats.onServiceStateChanged(mServiceState);
+
+        mImsStats.incTimeMillis(2000L);
+
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE)
+                .when(mServiceState)
+                .getDataNetworkType();
+        mImsStats.onServiceStateChanged(mServiceState);
+        mImsStats.conclude();
+
+        // Atom with termination info should be generated
+        ArgumentCaptor<ImsRegistrationTermination> terminationCaptor =
+                ArgumentCaptor.forClass(ImsRegistrationTermination.class);
+        verify(mPersistAtomsStorage).addImsRegistrationTermination(terminationCaptor.capture());
+        ImsRegistrationTermination termination = terminationCaptor.getValue();
+        assertEquals(CARRIER1_ID, termination.carrierId);
+        assertFalse(termination.isMultiSim);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, termination.ratAtEnd);
+        assertTrue(termination.setupFailed);
+        assertEquals(ImsReasonInfo.CODE_REGISTRATION_ERROR, termination.reasonCode);
+        assertEquals(999, termination.extraCode);
+        assertEquals("Timeout", termination.extraMessage);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    @SmallTest
+    public void conclude_serviceStateChanged_afterRatLte() throws Exception {
+        // IMS over LTE
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_VOICE,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_VIDEO,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_UT,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_SMS,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_ALL));
+
+        mImsStats.onImsUnregistered(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 999, "Timeout"));
+
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE)
+                .when(mServiceState)
+                .getDataNetworkType();
+        mImsStats.onServiceStateChanged(mServiceState);
+
+        mImsStats.incTimeMillis(2000L);
+
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+                .when(mServiceState)
+                .getDataNetworkType();
+        mImsStats.onServiceStateChanged(mServiceState);
+        mImsStats.conclude();
+
+        // Atom with termination info and durations should be generated
+        ArgumentCaptor<ImsRegistrationTermination> terminationCaptor =
+                ArgumentCaptor.forClass(ImsRegistrationTermination.class);
+        verify(mPersistAtomsStorage).addImsRegistrationTermination(terminationCaptor.capture());
+        ImsRegistrationTermination termination = terminationCaptor.getValue();
+        assertEquals(CARRIER1_ID, termination.carrierId);
+        assertFalse(termination.isMultiSim);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, termination.ratAtEnd);
+        assertTrue(termination.setupFailed);
+        assertEquals(ImsReasonInfo.CODE_REGISTRATION_ERROR, termination.reasonCode);
+        assertEquals(999, termination.extraCode);
+        assertEquals("Timeout", termination.extraMessage);
+
+        ArgumentCaptor<ImsRegistrationStats> statsCaptor =
+                ArgumentCaptor.forClass(ImsRegistrationStats.class);
+        verify(mPersistAtomsStorage).addImsRegistrationStats(statsCaptor.capture());
+        ImsRegistrationStats stats = statsCaptor.getValue();
+        assertEquals(CARRIER1_ID, stats.carrierId);
+        assertEquals(0, stats.simSlotIndex);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
+        assertEquals(2000L, stats.unregisteredMillis);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -385,6 +569,7 @@
         assertEquals(0L, stats.utAvailableMillis);
         assertEquals(0L, stats.smsCapableMillis);
         assertEquals(0L, stats.smsAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
         // ServiceStateStats should be notified
         verify(mServiceStateStats).onImsVoiceRegistrationChanged();
@@ -424,6 +609,7 @@
         assertEquals(0L, stats.utAvailableMillis);
         assertEquals(0L, stats.smsCapableMillis);
         assertEquals(0L, stats.smsAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
         // ServiceStateStats should be notified
         verify(mServiceStateStats, times(2)).onImsVoiceRegistrationChanged();
@@ -463,6 +649,7 @@
         assertEquals(0L, stats.utAvailableMillis);
         assertEquals(2000L, stats.smsCapableMillis);
         assertEquals(2000L, stats.smsAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
         // ServiceStateStats should not be notified
         verify(mServiceStateStats, never()).onImsVoiceRegistrationChanged();
@@ -500,6 +687,7 @@
         assertEquals(0L, stats.utAvailableMillis);
         assertEquals(0L, stats.smsCapableMillis);
         assertEquals(0L, stats.smsAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -530,6 +718,7 @@
         assertEquals(0L, statsLte.utAvailableMillis);
         assertEquals(0L, statsLte.smsCapableMillis);
         assertEquals(0L, statsLte.smsAvailableMillis);
+        assertEquals(1, statsLte.registeredTimes);
         ImsRegistrationStats statsWifi = captor.getAllValues().get(1);
         assertEquals(CARRIER1_ID, statsWifi.carrierId);
         assertEquals(0, statsWifi.simSlotIndex);
@@ -543,6 +732,65 @@
         assertEquals(0L, statsWifi.utAvailableMillis);
         assertEquals(0L, statsWifi.smsCapableMillis);
         assertEquals(0L, statsWifi.smsAvailableMillis);
+        assertEquals(0, statsWifi.registeredTimes);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    @SmallTest
+    public void onImsRegistered_afterImsRegistering() throws Exception {
+        mImsStats.onImsRegistering(TRANSPORT_TYPE_WWAN);
+        mImsStats.incTimeMillis(2000L);
+        mImsStats.onImsRegistered(mWwanAttributes);
+
+        // Registering duration should be counted
+        ArgumentCaptor<ImsRegistrationStats> captor =
+                ArgumentCaptor.forClass(ImsRegistrationStats.class);
+        verify(mPersistAtomsStorage).addImsRegistrationStats(captor.capture());
+        ImsRegistrationStats stats = captor.getValue();
+        assertEquals(CARRIER1_ID, stats.carrierId);
+        assertEquals(0, stats.simSlotIndex);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
+        assertEquals(0L, stats.registeredMillis);
+        assertEquals(2000L, stats.registeringMillis);
+        assertEquals(1, stats.registeredTimes);
+    }
+
+    @Test
+    @SmallTest
+    public void onImsRegistering_afterImsUnregistered() throws Exception {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE)
+                .when(mServiceState)
+                .getDataNetworkType();
+        mImsStats.onServiceStateChanged(mServiceState);
+
+        mImsStats.onImsUnregistered(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 999, "Timeout"));
+        mImsStats.incTimeMillis(2000L);
+        mImsStats.onImsRegistering(TRANSPORT_TYPE_WWAN);
+
+        // Atom with termination info should be generated
+        ArgumentCaptor<ImsRegistrationTermination> terminationCaptor =
+                ArgumentCaptor.forClass(ImsRegistrationTermination.class);
+        verify(mPersistAtomsStorage).addImsRegistrationTermination(terminationCaptor.capture());
+        ImsRegistrationTermination termination = terminationCaptor.getValue();
+        assertEquals(CARRIER1_ID, termination.carrierId);
+        assertFalse(termination.isMultiSim);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, termination.ratAtEnd);
+        assertTrue(termination.setupFailed);
+        assertEquals(ImsReasonInfo.CODE_REGISTRATION_ERROR, termination.reasonCode);
+        assertEquals(999, termination.extraCode);
+        assertEquals("Timeout", termination.extraMessage);
+
+        ArgumentCaptor<ImsRegistrationStats> statsCaptor =
+                ArgumentCaptor.forClass(ImsRegistrationStats.class);
+        verify(mPersistAtomsStorage).addImsRegistrationStats(statsCaptor.capture());
+        ImsRegistrationStats stats = statsCaptor.getValue();
+        assertEquals(CARRIER1_ID, stats.carrierId);
+        assertEquals(0, stats.simSlotIndex);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
+        assertEquals(2000L, stats.unregisteredMillis);
+        assertEquals(0, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -559,7 +807,7 @@
         ImsRegistrationTermination termination = captor.getValue();
         assertEquals(CARRIER1_ID, termination.carrierId);
         assertFalse(termination.isMultiSim);
-        assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, termination.ratAtEnd);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, termination.ratAtEnd);
         assertTrue(termination.setupFailed);
         assertEquals(ImsReasonInfo.CODE_REGISTRATION_ERROR, termination.reasonCode);
         assertEquals(999, termination.extraCode);
@@ -570,16 +818,26 @@
     @Test
     @SmallTest
     public void onImsUnregistered_setupFailureWithProgress() throws Exception {
-        mImsStats.onImsRegistering(REGISTRATION_TECH_LTE);
+        mImsStats.onImsRegistering(TRANSPORT_TYPE_WWAN);
         mImsStats.incTimeMillis(2000L);
         mImsStats.onImsUnregistered(
                 new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 999, "Timeout"));
 
-        // Atom with termination info should be generated
-        ArgumentCaptor<ImsRegistrationTermination> captor =
+        // Atom with termination info and durations should be generated
+        ArgumentCaptor<ImsRegistrationStats> statsCaptor =
+                ArgumentCaptor.forClass(ImsRegistrationStats.class);
+        verify(mPersistAtomsStorage).addImsRegistrationStats(statsCaptor.capture());
+        ImsRegistrationStats stats = statsCaptor.getValue();
+        assertEquals(CARRIER1_ID, stats.carrierId);
+        assertEquals(0, stats.simSlotIndex);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
+        assertEquals(0L, stats.registeredMillis);
+        assertEquals(2000L, stats.registeringMillis);
+
+        ArgumentCaptor<ImsRegistrationTermination> terminationCaptor =
                 ArgumentCaptor.forClass(ImsRegistrationTermination.class);
-        verify(mPersistAtomsStorage).addImsRegistrationTermination(captor.capture());
-        ImsRegistrationTermination termination = captor.getValue();
+        verify(mPersistAtomsStorage).addImsRegistrationTermination(terminationCaptor.capture());
+        ImsRegistrationTermination termination = terminationCaptor.getValue();
         assertEquals(CARRIER1_ID, termination.carrierId);
         assertFalse(termination.isMultiSim);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, termination.ratAtEnd);
@@ -615,6 +873,7 @@
         assertEquals(0L, stats.utAvailableMillis);
         assertEquals(0L, stats.smsCapableMillis);
         assertEquals(0L, stats.smsAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         ArgumentCaptor<ImsRegistrationTermination> terminationCaptor =
                 ArgumentCaptor.forClass(ImsRegistrationTermination.class);
         verify(mPersistAtomsStorage).addImsRegistrationTermination(terminationCaptor.capture());
@@ -642,7 +901,7 @@
         ImsRegistrationTermination termination = captor.getValue();
         assertEquals(CARRIER1_ID, termination.carrierId);
         assertFalse(termination.isMultiSim);
-        assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, termination.ratAtEnd);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, termination.ratAtEnd);
         assertTrue(termination.setupFailed);
         assertEquals(ImsReasonInfo.CODE_REGISTRATION_ERROR, termination.reasonCode);
         assertEquals(0, termination.extraCode);
@@ -668,7 +927,7 @@
         ImsRegistrationTermination termination = captor.getValue();
         assertEquals(CARRIER1_ID, termination.carrierId);
         assertFalse(termination.isMultiSim);
-        assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, termination.ratAtEnd);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, termination.ratAtEnd);
         assertTrue(termination.setupFailed);
         assertEquals(ImsReasonInfo.CODE_REGISTRATION_ERROR, termination.reasonCode);
         assertEquals(0, termination.extraCode);
@@ -841,6 +1100,7 @@
         assertEquals(0L, stats.utAvailableMillis);
         assertEquals(0L, stats.smsCapableMillis);
         assertEquals(0L, stats.smsAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         ArgumentCaptor<ImsRegistrationTermination> terminationCaptor =
                 ArgumentCaptor.forClass(ImsRegistrationTermination.class);
         verify(mPersistAtomsStorage).addImsRegistrationTermination(terminationCaptor.capture());
@@ -875,6 +1135,7 @@
         assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.rat);
         assertEquals(true, stats.isIwlanCrossSim);
         assertEquals(2000L, stats.registeredMillis);
+        assertEquals(1, stats.registeredTimes);
 
         ArgumentCaptor<ImsRegistrationTermination> terminationCaptor =
                 ArgumentCaptor.forClass(ImsRegistrationTermination.class);
@@ -964,14 +1225,11 @@
         mImsStats.onImsRegistered(mWwanAttributes);
         mImsStats.onImsCapabilitiesChanged(
                 REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_VOICE));
-        doReturn(
-                        new NetworkRegistrationInfo.Builder()
-                                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR)
-                                .setRegistrationState(
-                                        NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                                .build())
+
+        doReturn(TelephonyManager.NETWORK_TYPE_NR)
                 .when(mServiceState)
-                .getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+                .getDataNetworkType();
+
         mImsStats.onServiceStateChanged(mServiceState);
         assertEquals(TelephonyManager.NETWORK_TYPE_NR, mImsStats.getImsVoiceRadioTech());
     }
@@ -982,14 +1240,11 @@
         mImsStats.onImsRegistered(mWwanAttributes);
         mImsStats.onImsCapabilitiesChanged(
                 REGISTRATION_TECH_IWLAN, new MmTelCapabilities(CAPABILITY_TYPE_VOICE));
-        doReturn(
-                        new NetworkRegistrationInfo.Builder()
-                                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR)
-                                .setRegistrationState(
-                                        NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                                .build())
+
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN)
                 .when(mServiceState)
-                .getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+                .getDataNetworkType();
+
         mImsStats.onServiceStateChanged(mServiceState);
         assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, mImsStats.getImsVoiceRadioTech());
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/InProgressCallSessionTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/InProgressCallSessionTest.java
index 116293c..3ad2d18 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/InProgressCallSessionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/InProgressCallSessionTest.java
@@ -20,7 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nano.TelephonyProto;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/InProgressSmsSessionTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/InProgressSmsSessionTest.java
index a8ac50a..4f52513 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/InProgressSmsSessionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/InProgressSmsSessionTest.java
@@ -20,7 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nano.TelephonyProto;
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 d4e1b86..0426737 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
@@ -23,6 +23,7 @@
 import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION;
+import static com.android.internal.telephony.util.TelephonyUtils.IS_DEBUGGABLE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -36,12 +37,14 @@
 
 import android.app.StatsManager;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.StatsEvent;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
@@ -51,6 +54,7 @@
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccPort;
+import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.uicc.UiccSlot;
 
 import org.junit.After;
@@ -66,6 +70,8 @@
                     .setCoolDownMillis(24L * 3600L * 1000L)
                     .build();
     private static final long MIN_COOLDOWN_MILLIS = 23L * 3600L * 1000L;
+    private static final long CELL_SERVICE_MIN_COOLDOWN_MILLIS =
+            IS_DEBUGGABLE ? 4L *  60L * 1000L : MIN_COOLDOWN_MILLIS;
     private static final long MIN_CALLS_PER_BUCKET = 5L;
 
     // NOTE: these fields are currently 32-bit internally and padded to 64-bit by TelephonyManager
@@ -91,6 +97,8 @@
     private UiccCard mActiveCard;
     private UiccPort mActivePort;
     private ServiceStateStats mServiceStateStats;
+    private VonrHelper mVonrHelper;
+    private FeatureFlags mFeatureFlags;
 
     private MetricsCollector mMetricsCollector;
 
@@ -103,8 +111,11 @@
         mActiveCard = mock(UiccCard.class);
         mActivePort = mock(UiccPort.class);
         mServiceStateStats = mock(ServiceStateStats.class);
+        mVonrHelper = mock(VonrHelper.class);
+        mFeatureFlags = mock(FeatureFlags.class);
         mMetricsCollector =
-                new MetricsCollector(mContext, mPersistAtomsStorage, mDeviceStateHelper);
+                new MetricsCollector(mContext, mPersistAtomsStorage,
+                        mDeviceStateHelper, mVonrHelper, mFeatureFlags);
         doReturn(mSST).when(mSecondPhone).getServiceStateTracker();
         doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
     }
@@ -119,6 +130,9 @@
     @SmallTest
     public void onPullAtom_simSlotState_bothSimPresent() {
         // these have been tested extensively in SimSlotStateTest, here we verify atom generation
+        UiccProfile activeProfile = mock(UiccProfile.class);
+        doReturn(4).when(activeProfile).getNumApplications();
+        doReturn(activeProfile).when(mActivePort).getUiccProfile();
         doReturn(true).when(mPhysicalSlot).isActive();
         doReturn(CardState.CARDSTATE_PRESENT).when(mPhysicalSlot).getCardState();
         doReturn(false).when(mPhysicalSlot).isEuicc();
@@ -126,7 +140,6 @@
         doReturn(CardState.CARDSTATE_PRESENT).when(mEsimSlot).getCardState();
         doReturn(true).when(mEsimSlot).isEuicc();
         doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
-        doReturn(4).when(mActivePort).getNumApplications();
         doReturn(new UiccPort[] {mActivePort}).when(mActiveCard).getUiccPortList();
         doReturn(new UiccSlot[] {mPhysicalSlot, mEsimSlot}).when(mUiccController).getUiccSlots();
         doReturn(mPhysicalSlot).when(mUiccController).getUiccSlot(eq(0));
@@ -395,7 +408,8 @@
 
         assertThat(actualAtoms).hasSize(0);
         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
-        verify(mPersistAtomsStorage, times(1)).getCellularServiceStates(eq(MIN_COOLDOWN_MILLIS));
+        verify(mPersistAtomsStorage, times(1)).getCellularServiceStates(
+                eq(CELL_SERVICE_MIN_COOLDOWN_MILLIS));
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java
index e03dfe4..5ca73e5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java
@@ -38,7 +38,8 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsManager;
 import android.telephony.ims.ImsMmTelManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.Phone;
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 3307813..475c90b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -55,7 +55,8 @@
 import android.telephony.TelephonyProtoEnums;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.SipDelegateManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.TelephonyTest;
@@ -159,6 +160,8 @@
     private CellularServiceState[] mServiceStates;
 
     // IMS registrations for slot 0 and 1
+    private ImsRegistrationStats mImsRegStatsUnregisteredLte0;
+    private ImsRegistrationStats mImsRegStatsRegisteringLte0;
     private ImsRegistrationStats mImsRegistrationStatsLte0;
     private ImsRegistrationStats mImsRegistrationStatsWifi0;
     private ImsRegistrationStats mImsRegistrationStatsLte1;
@@ -503,6 +506,42 @@
                     mServiceState5Proto
                 };
 
+        // IMS over LTE on slot 0, unregistered for 5 seconds at the registered state
+        mImsRegStatsUnregisteredLte0 = new ImsRegistrationStats();
+        mImsRegStatsUnregisteredLte0.carrierId = CARRIER1_ID;
+        mImsRegStatsUnregisteredLte0.simSlotIndex = 0;
+        mImsRegStatsUnregisteredLte0.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mImsRegStatsUnregisteredLte0.registeredMillis = 0;
+        mImsRegStatsUnregisteredLte0.voiceCapableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.voiceAvailableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.smsCapableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.smsAvailableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.videoCapableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.videoAvailableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.utCapableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.utAvailableMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.registeringMillis = 0;
+        mImsRegStatsUnregisteredLte0.unregisteredMillis = 5000L;
+        mImsRegStatsUnregisteredLte0.registeredTimes = 0;
+
+        // IMS over LTE on slot 0, registering for 5 seconds at the registered state
+        mImsRegStatsRegisteringLte0 = new ImsRegistrationStats();
+        mImsRegStatsRegisteringLte0.carrierId = CARRIER1_ID;
+        mImsRegStatsRegisteringLte0.simSlotIndex = 0;
+        mImsRegStatsRegisteringLte0.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mImsRegStatsRegisteringLte0.registeredMillis = 0;
+        mImsRegStatsRegisteringLte0.voiceCapableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.voiceAvailableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.smsCapableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.smsAvailableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.videoCapableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.videoAvailableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.utCapableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.utAvailableMillis = 5000L;
+        mImsRegStatsRegisteringLte0.registeringMillis = 5000L;
+        mImsRegStatsRegisteringLte0.unregisteredMillis = 0;
+        mImsRegStatsRegisteringLte0.registeredTimes = 1;
+
         // IMS over LTE on slot 0, registered for 5 seconds
         mImsRegistrationStatsLte0 = new ImsRegistrationStats();
         mImsRegistrationStatsLte0.carrierId = CARRIER1_ID;
@@ -517,6 +556,9 @@
         mImsRegistrationStatsLte0.videoAvailableMillis = 5000L;
         mImsRegistrationStatsLte0.utCapableMillis = 5000L;
         mImsRegistrationStatsLte0.utAvailableMillis = 5000L;
+        mImsRegistrationStatsLte0.registeringMillis = 0;
+        mImsRegistrationStatsLte0.unregisteredMillis = 0;
+        mImsRegistrationStatsLte0.registeredTimes = 0;
 
         // IMS over WiFi on slot 0, registered for 10 seconds (voice only)
         mImsRegistrationStatsWifi0 = new ImsRegistrationStats();
@@ -541,6 +583,9 @@
         mImsRegistrationStatsLte1.videoAvailableMillis = 20000L;
         mImsRegistrationStatsLte1.utCapableMillis = 20000L;
         mImsRegistrationStatsLte1.utAvailableMillis = 20000L;
+        mImsRegistrationStatsLte1.registeringMillis = 0;
+        mImsRegistrationStatsLte1.unregisteredMillis = 0;
+        mImsRegistrationStatsLte1.registeredTimes = 0;
 
         // IMS terminations on LTE
         mImsRegistrationTerminationLte = new ImsRegistrationTermination();
@@ -566,8 +611,13 @@
 
         mImsRegistrationStats =
                 new ImsRegistrationStats[] {
-                    mImsRegistrationStatsLte0, mImsRegistrationStatsWifi0, mImsRegistrationStatsLte1
+                    mImsRegStatsUnregisteredLte0,
+                    mImsRegStatsRegisteringLte0,
+                    mImsRegistrationStatsLte0,
+                    mImsRegistrationStatsWifi0,
+                    mImsRegistrationStatsLte1
                 };
+
         mImsRegistrationTerminations =
                 new ImsRegistrationTermination[] {
                     mImsRegistrationTerminationLte, mImsRegistrationTerminationWifi
@@ -1017,7 +1067,8 @@
         mSatelliteProvision1.isCanceled = false;
 
         mSatelliteProvision2 = new SatelliteProvision();
-        mSatelliteProvision2.resultCode = SatelliteProtoEnums.SATELLITE_SERVICE_NOT_PROVISIONED;
+        mSatelliteProvision2.resultCode =
+                SatelliteProtoEnums.SATELLITE_SERVICE_NOT_PROVISIONED;
         mSatelliteProvision2.provisioningTimeSec = 0;
         mSatelliteProvision2.isProvisionRequest = false;
         mSatelliteProvision2.isCanceled = true;
@@ -1033,6 +1084,9 @@
         mSatelliteSosMessageRecommender1.isImsRegistered = false;
         mSatelliteSosMessageRecommender1.cellularServiceState =
                 TelephonyProtoEnums.SERVICE_STATE_OUT_OF_SERVICE;
+        mSatelliteSosMessageRecommender1.isMultiSim = true;
+        mSatelliteSosMessageRecommender1.recommendingHandoverType = 1;
+        mSatelliteSosMessageRecommender1.isSatelliteAllowedInCurrentLocation = true;
         mSatelliteSosMessageRecommender1.count = 1;
 
         mSatelliteSosMessageRecommender2 = new SatelliteSosMessageRecommender();
@@ -1041,6 +1095,9 @@
         mSatelliteSosMessageRecommender2.isImsRegistered = true;
         mSatelliteSosMessageRecommender2.cellularServiceState =
                 TelephonyProtoEnums.SERVICE_STATE_POWER_OFF;
+        mSatelliteSosMessageRecommender2.isMultiSim = false;
+        mSatelliteSosMessageRecommender2.recommendingHandoverType = 0;
+        mSatelliteSosMessageRecommender2.isSatelliteAllowedInCurrentLocation = true;
         mSatelliteSosMessageRecommender2.count = 1;
 
         mSatelliteSosMessageRecommenders =
@@ -1136,6 +1193,8 @@
         mServiceState5Proto = null;
         mServiceSwitches = null;
         mServiceStates = null;
+        mImsRegStatsUnregisteredLte0 = null;
+        mImsRegStatsRegisteringLte0 = null;
         mImsRegistrationStatsLte0 = null;
         mImsRegistrationStatsWifi0 = null;
         mImsRegistrationStatsLte1 = null;
@@ -1745,7 +1804,7 @@
         mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsLte0));
         mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
 
-        // Service state and service switch should be added successfully
+        // mImsRegistrationStatsLte0 should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
         assertProtoArrayEquals(new ImsRegistrationStats[] {mImsRegistrationStatsLte0}, regStats);
@@ -1761,7 +1820,7 @@
         mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsWifi0));
         mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
 
-        // Service state and service switch should be added successfully
+        // mImsRegistrationStatsLte0 and mImsRegistrationStatsWifi0 should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
         assertProtoArrayEqualsIgnoringOrder(
@@ -1771,18 +1830,34 @@
 
     @Test
     @SmallTest
-    public void addImsRegistrationStats_updateExistingEntries() throws Exception {
-        createTestFile(START_TIME_MILLIS);
-        ImsRegistrationStats newImsRegistrationStatsLte0 = copyOf(mImsRegistrationStatsLte0);
+    public void addImsRegistrationStats_withExistingRegisteringEntries() throws Exception {
+        createEmptyTestFile();
         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
-
-        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsLte0));
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegStatsRegisteringLte0));
         mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
 
-        // mImsRegistrationStatsLte0's durations should be doubled
+        // mImsRegStatsRegisteringLte0's info should be added successfully
         verifyCurrentStateSavedToFileOnce();
-        ImsRegistrationStats[] serviceStates = mPersistAtomsStorage.getImsRegistrationStats(0L);
-        newImsRegistrationStatsLte0.registeredMillis *= 2;
+        ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new ImsRegistrationStats[] {mImsRegStatsRegisteringLte0},
+                regStats);
+    }
+
+    @Test
+    @SmallTest
+    public void addImsRegistrationStats_updateExistingEntries() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+        ImsRegistrationStats newImsRegistrationStatsLte0 = copyOf(mImsRegStatsUnregisteredLte0);
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegStatsUnregisteredLte0));
+        mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
+
+        // mImsRegStatsUnregisteredLte0's durations should be doubled
+        verifyCurrentStateSavedToFileOnce();
+        ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
+        newImsRegistrationStatsLte0.unregisteredMillis *= 2;
         newImsRegistrationStatsLte0.voiceCapableMillis *= 2;
         newImsRegistrationStatsLte0.voiceAvailableMillis *= 2;
         newImsRegistrationStatsLte0.smsCapableMillis *= 2;
@@ -1794,10 +1869,12 @@
         assertProtoArrayEqualsIgnoringOrder(
                 new ImsRegistrationStats[] {
                     newImsRegistrationStatsLte0,
+                    mImsRegStatsRegisteringLte0,
+                    mImsRegistrationStatsLte0,
                     mImsRegistrationStatsWifi0,
                     mImsRegistrationStatsLte1
                 },
-                serviceStates);
+                regStats);
     }
 
     @Test
@@ -1826,6 +1903,39 @@
 
     @Test
     @SmallTest
+    public void addImsRegistrationStats_withExistingDurationEntries() throws Exception {
+        createEmptyTestFile();
+        ImsRegistrationStats newImsRegStatsLte0 = copyOf(mImsRegistrationStatsLte0);
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegStatsUnregisteredLte0));
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegStatsRegisteringLte0));
+        mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsLte0));
+        mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
+
+        // UnregisteredMillis, registeringMillis and registeredTimes should be added successfully
+        // capable and available durations should be tripled
+        verifyCurrentStateSavedToFileOnce();
+        ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
+        newImsRegStatsLte0.unregisteredMillis += newImsRegStatsLte0.registeredMillis;
+        newImsRegStatsLte0.registeringMillis += newImsRegStatsLte0.registeredMillis;
+        newImsRegStatsLte0.registeredTimes += 1;
+        newImsRegStatsLte0.voiceCapableMillis *= 3;
+        newImsRegStatsLte0.voiceAvailableMillis *= 3;
+        newImsRegStatsLte0.smsCapableMillis *= 3;
+        newImsRegStatsLte0.smsAvailableMillis *= 3;
+        newImsRegStatsLte0.videoCapableMillis *= 3;
+        newImsRegStatsLte0.videoAvailableMillis *= 3;
+        newImsRegStatsLte0.utCapableMillis *= 3;
+        newImsRegStatsLte0.utAvailableMillis *= 3;
+        assertProtoArrayEqualsIgnoringOrder(
+                new ImsRegistrationStats[] {
+                    newImsRegStatsLte0
+                },
+                regStats);
+    }
+
+    @Test
+    @SmallTest
     public void addImsRegistrationTermination_emptyProto() throws Exception {
         createEmptyTestFile();
 
@@ -1935,7 +2045,11 @@
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new ImsRegistrationStats[] {
-                    mImsRegistrationStatsLte0, mImsRegistrationStatsWifi0, mImsRegistrationStatsLte1
+                    mImsRegStatsUnregisteredLte0,
+                    mImsRegStatsRegisteringLte0,
+                    mImsRegistrationStatsLte0,
+                    mImsRegistrationStatsWifi0,
+                    mImsRegistrationStatsLte1
                 },
                 stats1);
         assertProtoArrayEquals(new ImsRegistrationStats[0], stats2);
@@ -4688,7 +4802,11 @@
                     == expectedStats.isDisplaySosMessageSent
                     && stats.countOfTimerStarted == expectedStats.countOfTimerStarted
                     && stats.isImsRegistered == expectedStats.isImsRegistered
-                    && stats.cellularServiceState == expectedStats.cellularServiceState) {
+                    && stats.cellularServiceState == expectedStats.cellularServiceState
+                    && stats.isMultiSim == expectedStats.isMultiSim
+                    && stats.recommendingHandoverType == expectedStats.recommendingHandoverType
+                    && stats.isSatelliteAllowedInCurrentLocation
+                    == expectedStats.isSatelliteAllowedInCurrentLocation) {
                 actualCount = stats.count;
             }
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/RcsStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/RcsStatsTest.java
index 3a941e3..d417214 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/RcsStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/RcsStatsTest.java
@@ -30,10 +30,11 @@
 import android.telephony.ims.DelegateRegistrationState;
 import android.telephony.ims.FeatureTagState;
 import android.telephony.ims.SipDelegateManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.ims.rcs.uce.util.FeatureTags;
 import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.TelephonyTest;
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 22032ae..959b643 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
@@ -196,7 +196,8 @@
     public void onSatelliteProvisionMetrics_withAtoms() throws Exception {
         SatelliteStats.SatelliteProvisionParams param =
                 new SatelliteStats.SatelliteProvisionParams.Builder()
-                        .setResultCode(SatelliteProtoEnums.SATELLITE_SERVICE_PROVISION_IN_PROGRESS)
+                        .setResultCode(
+                                SatelliteProtoEnums.SATELLITE_SERVICE_PROVISION_IN_PROGRESS)
                         .setProvisioningTimeSec(5 * 1000)
                         .setIsProvisionRequest(true)
                         .setIsCanceled(false)
@@ -224,6 +225,9 @@
                         .setCountOfTimerStarted(5)
                         .setImsRegistered(false)
                         .setCellularServiceState(TelephonyProtoEnums.SERVICE_STATE_OUT_OF_SERVICE)
+                        .setIsMultiSim(false)
+                        .setRecommendingHandoverType(0)
+                        .setIsSatelliteAllowedInCurrentLocation(true)
                         .build();
 
         mSatelliteStats.onSatelliteSosMessageRecommender(param);
@@ -237,6 +241,10 @@
         assertEquals(param.getCountOfTimerStarted(), stats.countOfTimerStarted);
         assertEquals(param.isImsRegistered(), stats.isImsRegistered);
         assertEquals(param.getCellularServiceState(), stats.cellularServiceState);
+        assertEquals(param.isMultiSim(), stats.isMultiSim);
+        assertEquals(param.getRecommendingHandoverType(), stats.recommendingHandoverType);
+        assertEquals(param.isSatelliteAllowedInCurrentLocation(),
+                stats.isSatelliteAllowedInCurrentLocation);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
index 6ee2e54..16dab44 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -44,10 +45,12 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.data.DataNetwork;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
@@ -58,6 +61,9 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.util.Collections;
+import java.util.Set;
+
 public class ServiceStateStatsTest extends TelephonyTest {
     private static final long START_TIME_MILLIS = 2000L;
     private static final int CARRIER1_ID = 1;
@@ -424,9 +430,12 @@
     public void onInternetDataNetworkDisconnected() throws Exception {
          // Using default service state for LTE
         mServiceStateStats.onServiceStateChanged(mServiceState);
+        // Set internet network connected
+        mServiceStateStats.onConnectedInternetDataNetworksChanged(Set.of(mock(DataNetwork.class)));
+        clearInvocations(mPersistAtomsStorage);
 
         mServiceStateStats.incTimeMillis(100L);
-        mServiceStateStats.onInternetDataNetworkDisconnected();
+        mServiceStateStats.onConnectedInternetDataNetworksChanged(Collections.emptySet());
         mServiceStateStats.incTimeMillis(200L);
         mServiceStateStats.conclude();
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/SimSlotStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/SimSlotStateTest.java
index 0a64ee3..4ab85f1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/SimSlotStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SimSlotStateTest.java
@@ -23,12 +23,13 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccPort;
+import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.uicc.UiccSlot;
 
 import org.junit.After;
@@ -71,8 +72,12 @@
         doReturn(CardState.CARDSTATE_PRESENT).when(mEsimSlot).getCardState();
         doReturn(true).when(mEsimSlot).isEuicc();
 
-        doReturn(0).when(mInactivePort).getNumApplications();
-        doReturn(4).when(mActivePort).getNumApplications();
+        UiccProfile inactiveProfile = mock(UiccProfile.class);
+        UiccProfile activeProfile = mock(UiccProfile.class);
+        doReturn(0).when(inactiveProfile).getNumApplications();
+        doReturn(4).when(activeProfile).getNumApplications();
+        doReturn(inactiveProfile).when(mInactivePort).getUiccProfile();
+        doReturn(activeProfile).when(mActivePort).getUiccProfile();
 
         doReturn(new UiccPort[]{mInactivePort}).when(mInactiveCard).getUiccPortList();
         doReturn(new UiccPort[]{mActivePort}).when(mActiveCard).getUiccPortList();
@@ -94,6 +99,8 @@
         assertEquals(0, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -108,6 +115,8 @@
         assertEquals(0, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -122,6 +131,8 @@
         assertEquals(0, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -136,6 +147,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -151,6 +164,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(1, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -166,6 +181,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -182,6 +199,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -197,6 +216,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -212,6 +233,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -227,11 +250,31 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(1, state.numActiveSims);
         assertEquals(1, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
     @Test
     @SmallTest
+    public void testSingleSim_esimCardWithMultipleProfiles() {
+        doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
+        doReturn(new UiccPort[]{mActivePort, mActivePort}).when(mActiveCard).getUiccPortList();
+        setupSingleSim(mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+        boolean isMultiSim = SimSlotState.isMultiSim();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(2, state.numActiveSims);
+        assertEquals(2, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(1, state.numActiveMepSlots);
+        assertTrue(isMultiSim);
+    }
+
+    @Test
+    @SmallTest
     public void testDsdsSingleSimMode_noSimCard() {
         setupDualSim(mEmptySlot, mInactiveSlot);
 
@@ -241,6 +284,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -255,6 +300,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(1, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -270,6 +317,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -285,6 +334,8 @@
         assertEquals(1, state.numActiveSlots);
         assertEquals(1, state.numActiveSims);
         assertEquals(1, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -300,6 +351,8 @@
         assertEquals(2, state.numActiveSlots);
         assertEquals(0, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -315,6 +368,8 @@
         assertEquals(2, state.numActiveSlots);
         assertEquals(1, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
@@ -330,11 +385,31 @@
         assertEquals(2, state.numActiveSlots);
         assertEquals(1, state.numActiveSims);
         assertEquals(1, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim);
     }
 
     @Test
     @SmallTest
+    public void testDsds_esimCardWithMultipleProfiles() {
+        doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
+        doReturn(new UiccPort[]{mActivePort, mActivePort}).when(mActiveCard).getUiccPortList();
+        setupDualSim(mEmptySlot, mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+        boolean isMultiSim = SimSlotState.isMultiSim();
+
+        assertEquals(2, state.numActiveSlots);
+        assertEquals(2, state.numActiveSims);
+        assertEquals(2, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(1, state.numActiveMepSlots);
+        assertTrue(isMultiSim);
+    }
+
+    @Test
+    @SmallTest
     public void testDsds_physicalAndEsimCardWithProfile() {
         doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
         setupDualSim(mPhysicalSlot, mEsimSlot);
@@ -345,6 +420,8 @@
         assertEquals(2, state.numActiveSlots);
         assertEquals(2, state.numActiveSims);
         assertEquals(1, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertTrue(isMultiSim);
     }
 
@@ -359,6 +436,8 @@
         assertEquals(2, state.numActiveSlots);
         assertEquals(2, state.numActiveSims);
         assertEquals(0, state.numActiveEsims);
+        assertEquals(0, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertTrue(isMultiSim);
     }
 
@@ -382,6 +461,8 @@
         assertEquals(2, state.numActiveSlots);
         assertEquals(1, state.numActiveSims);
         assertEquals(1, state.numActiveEsims);
+        assertEquals(1, state.numActiveEsimSlots);
+        assertEquals(0, state.numActiveMepSlots);
         assertFalse(isMultiSim); // one Uicc Port does not have active sim profile
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
index c26dd22..71b975a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
@@ -46,9 +46,10 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CarrierResolver;
 import com.android.internal.telephony.GsmCdmaConnection;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
index 9b793d9..58cc516 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
@@ -44,6 +44,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.annotation.NonNull;
 import android.os.Looper;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetworkType;
@@ -57,7 +58,8 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.DriverCall;
@@ -68,6 +70,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
@@ -77,6 +80,7 @@
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccPort;
+import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.uicc.UiccSlot;
 
 import org.junit.After;
@@ -113,12 +117,14 @@
     private GsmCdmaCall mCsCall1;
     private ImsPhoneCall mImsCall0;
     private ImsPhoneCall mImsCall1;
+    private VonrHelper mVonrHelper;
 
     private static class TestableVoiceCallSessionStats extends VoiceCallSessionStats {
         private long mTimeMillis = 0L;
 
-        TestableVoiceCallSessionStats(int phoneId, Phone phone) {
-            super(phoneId, phone);
+        TestableVoiceCallSessionStats(int phoneId, Phone phone,
+                @NonNull FeatureFlags featureFlags) {
+            super(phoneId, phone, featureFlags);
         }
 
         @Override
@@ -159,6 +165,7 @@
         mCsCall1 = mock(GsmCdmaCall.class);
         mImsCall0 = mock(ImsPhoneCall.class);
         mImsCall1 = mock(ImsPhoneCall.class);
+        mVonrHelper = mock(VonrHelper.class);
         replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mSecondPhone});
         doReturn(CARRIER_ID_SLOT_0).when(mPhone).getCarrierId();
         // mPhone's mContext/mSST/mServiceState has been set up by TelephonyTest
@@ -179,8 +186,13 @@
         doReturn(true).when(mEsimSlot).isActive();
         doReturn(CardState.CARDSTATE_PRESENT).when(mEsimSlot).getCardState();
         doReturn(true).when(mEsimSlot).isEuicc();
-        doReturn(0).when(mInactivePort).getNumApplications();
-        doReturn(4).when(mActivePort).getNumApplications();
+
+        UiccProfile inactiveProfile = mock(UiccProfile.class);
+        UiccProfile activeProfile = mock(UiccProfile.class);
+        doReturn(0).when(inactiveProfile).getNumApplications();
+        doReturn(4).when(activeProfile).getNumApplications();
+        doReturn(inactiveProfile).when(mInactivePort).getUiccProfile();
+        doReturn(activeProfile).when(mActivePort).getUiccProfile();
 
         doReturn(new UiccSlot[] {mPhysicalSlot}).when(mUiccController).getUiccSlots();
         doReturn(mPhysicalSlot).when(mUiccController).getUiccSlot(eq(0));
@@ -206,10 +218,14 @@
             Looper.prepare();
         }
 
-        mVoiceCallSessionStats0 = new TestableVoiceCallSessionStats(0, mPhone);
+        doReturn(mVonrHelper).when(mMetricsCollector).getVonrHelper();
+
+        mVoiceCallSessionStats0 = new TestableVoiceCallSessionStats(0, mPhone, mFeatureFlags);
         mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
-        mVoiceCallSessionStats1 = new TestableVoiceCallSessionStats(1, mSecondPhone);
+        mVoiceCallSessionStats1 = new TestableVoiceCallSessionStats(1, mSecondPhone, mFeatureFlags);
         mVoiceCallSessionStats1.onServiceStateChanged(mSecondServiceState);
+
+        doReturn(true).when(mFeatureFlags).vonrEnabledMetric();
     }
 
     @After
@@ -2680,6 +2696,63 @@
         assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
     }
 
+    @Test
+    @SmallTest
+    public void singleCall_vonrEnabled() {
+        setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(false).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(0L).when(mImsConnection0).getDurationMillis();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(true).when(mVonrHelper).getVonrEnabled(anyInt());
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_REMOTE_CALL_DECLINE);
+        expectedCall.setupDurationMillis = 200;
+        expectedCall.setupFailed = true;
+        expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_EVS_SWB;
+        expectedCall.mainCodecQuality =
+                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_SUPER_WIDEBAND;
+        expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        expectedCall.callDuration = VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_UNKNOWN;
+        expectedCall.vonrEnabled = true;
+        VoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
+        final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.DIALING).when(mImsCall0).getState();
+        doReturn(Call.State.DIALING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        doReturn(Call.State.ALERTING).when(mImsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_REMOTE_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
     private AtomicReference<VoiceCallRatUsage[]> setupRatUsageCapture() {
         final AtomicReference<VoiceCallRatUsage[]> ratUsage = new AtomicReference<>(null);
         doAnswer(
@@ -2767,6 +2840,7 @@
         call.isRoaming = false;
         call.setupBeginMillis = 0L;
         call.signalStrengthAtEnd = 2;
+        call.vonrEnabled = false;
         return call;
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VonrHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VonrHelperTest.java
new file mode 100644
index 0000000..ddc29de
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VonrHelperTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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 com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.annotation.NonNull;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class VonrHelperTest extends TelephonyTest {
+    private static final int SUBID = 1;
+
+    private static class TestableVonrHelper extends VonrHelper {
+        TestableVonrHelper(@NonNull FeatureFlags featureFlags) {
+            super(featureFlags);
+        }
+
+        @Override
+        public void updateVonrEnabledState() {
+            mVonrRunnable.run();
+        }
+    }
+
+    private TestableVonrHelper mVonrHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        doReturn(SUBID).when(mPhone).getSubId();
+        doReturn(false).when(mTelephonyManager).isVoNrEnabled();
+        mVonrHelper = new TestableVonrHelper(mFeatureFlags);
+        doReturn(true).when(mFeatureFlags).vonrEnabledMetric();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void vonr_enabled() {
+        doReturn(true).when(mTelephonyManager).isVoNrEnabled();
+
+        mVonrHelper.updateVonrEnabledState();
+
+        assertThat(mVonrHelper.getVonrEnabled(SUBID)).isTrue();
+    }
+
+    @Test
+    @SmallTest
+    public void vonr_disabled() {
+        mVonrHelper.updateVonrEnabledState();
+
+        assertThat(mVonrHelper.getVonrEnabled(SUBID)).isFalse();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/TEST_MAPPING b/tests/telephonytests/src/com/android/internal/telephony/nitz/TEST_MAPPING
new file mode 100644
index 0000000..4063803
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksTelephonyTests",
+      "options": [
+        {
+          "include-filter": "com.android.internal.telephony.nitz."
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
index baa00c1..76cd4ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
@@ -280,7 +280,7 @@
     public void testReportIncomingDatagramCount() {
         mTestStats.initializeParams();
 
-        int result = SatelliteManager.SATELLITE_ERROR_NONE;
+        int result = SatelliteManager.SATELLITE_RESULT_SUCCESS;
         for (int i = 0; i < 10; i++) {
             mControllerMetricsStatsUT.reportIncomingDatagramCount(result);
         }
@@ -303,7 +303,7 @@
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
         mTestStats.initializeParams();
 
-        result = SatelliteManager.SATELLITE_SERVER_ERROR;
+        result = SatelliteManager.SATELLITE_RESULT_SERVER_ERROR;
         for (int i = 0; i < 10; i++) {
             mControllerMetricsStatsUT.reportIncomingDatagramCount(result);
         }
@@ -326,7 +326,7 @@
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
         mTestStats.initializeParams();
 
-        result = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+        result = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
         for (int i = 0; i < 10; i++) {
             mControllerMetricsStatsUT.reportIncomingDatagramCount(result);
         }
@@ -354,7 +354,7 @@
     public void testReportProvisionCount() {
         mTestStats.initializeParams();
 
-        int result = SatelliteManager.SATELLITE_ERROR_NONE;
+        int result = SatelliteManager.SATELLITE_RESULT_SUCCESS;
         for (int i = 0; i < 10; i++) {
             mControllerMetricsStatsUT.reportProvisionCount(result);
         }
@@ -377,7 +377,7 @@
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
         mTestStats.initializeParams();
 
-        result = SatelliteManager.SATELLITE_SERVER_ERROR;
+        result = SatelliteManager.SATELLITE_RESULT_SERVER_ERROR;
         for (int i = 0; i < 10; i++) {
             mControllerMetricsStatsUT.reportProvisionCount(result);
         }
@@ -400,7 +400,7 @@
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
         mTestStats.initializeParams();
 
-        result = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+        result = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
         for (int i = 0; i < 10; i++) {
             mControllerMetricsStatsUT.reportProvisionCount(result);
         }
@@ -428,7 +428,7 @@
     public void testReportDeprovisionCount() {
         mTestStats.initializeParams();
 
-        int result = SatelliteManager.SATELLITE_ERROR_NONE;
+        int result = SatelliteManager.SATELLITE_RESULT_SUCCESS;
         for (int i = 0; i < 10; i++) {
             mControllerMetricsStatsUT.reportDeprovisionCount(result);
         }
@@ -451,7 +451,7 @@
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
         mTestStats.initializeParams();
 
-        result = SatelliteManager.SATELLITE_SERVER_ERROR;
+        result = SatelliteManager.SATELLITE_RESULT_SERVER_ERROR;
         for (int i = 0; i < 10; i++) {
             mControllerMetricsStatsUT.reportDeprovisionCount(result);
         }
@@ -474,7 +474,7 @@
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
         mTestStats.initializeParams();
 
-        result = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+        result = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
         for (int i = 0; i < 10; i++) {
             mControllerMetricsStatsUT.reportDeprovisionCount(result);
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
index bc1d767..37ff69a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
@@ -16,30 +16,46 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
+
 import static com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Looper;
 import android.os.Message;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.internal.R;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
@@ -53,8 +69,12 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -65,6 +85,10 @@
     private static final int DATAGRAM_TYPE2 = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
     private static final String TEST_MESSAGE = "This is a test datagram message";
     private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1);
+    private static final int TEST_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMEOUT_MILLIS =
+            (int) TimeUnit.SECONDS.toMillis(180);
+    private static final long TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS =
+            TimeUnit.SECONDS.toMillis(60);
 
     private DatagramDispatcher mDatagramDispatcherUT;
     private TestDatagramDispatcher mTestDemoModeDatagramDispatcher;
@@ -73,12 +97,29 @@
     @Mock private DatagramReceiver mMockDatagramReceiver;
     @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
     @Mock private ControllerMetricsStats mMockControllerMetricsStats;
+    @Mock private SatelliteSessionController mMockSatelliteSessionController;
 
     /** Variables required to send datagram in the unit tests. */
     LinkedBlockingQueue<Integer> mResultListener;
     SatelliteDatagram mDatagram;
     InOrder mInOrder;
 
+    private static final long TIMEOUT = 500;
+    private List<Integer> mIntegerConsumerResult = new ArrayList<>();
+    private Semaphore mIntegerConsumerSemaphore = new Semaphore(0);
+    private Consumer<Integer> mIntegerConsumer = integer -> {
+        logd("mIntegerConsumer: integer=" + integer);
+        mIntegerConsumerResult.add(integer);
+        try {
+            mIntegerConsumerSemaphore.release();
+        } catch (Exception ex) {
+            loge("mIntegerConsumer: Got exception in releasing semaphore, ex=" + ex);
+        }
+    };
+
+    private final int mConfigSendSatelliteDatagramToModemInDemoMode =
+            R.bool.config_send_satellite_datagram_to_modem_in_demo_mode;
+
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
@@ -93,7 +134,10 @@
                 mMockSatelliteModemInterface);
         replaceInstance(ControllerMetricsStats.class, "sInstance", null,
                 mMockControllerMetricsStats);
+        replaceInstance(SatelliteSessionController.class, "sInstance", null,
+                mMockSatelliteSessionController);
 
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         mDatagramDispatcherUT = DatagramDispatcher.make(mContext, Looper.myLooper(),
                 mMockDatagramController);
         mTestDemoModeDatagramDispatcher = new TestDatagramDispatcher(mContext, Looper.myLooper(),
@@ -118,8 +162,7 @@
     }
 
     @Test
-    public void testSendSatelliteDatagram_usingSatelliteModemInterface_success() throws  Exception {
-        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+    public void testSendSatelliteDatagram_success() throws  Exception {
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[3];
 
@@ -129,40 +172,196 @@
             return null;
         }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
                 anyBoolean(), anyBoolean(), any(Message.class));
+        doReturn(true).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected();
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        mResultListener.clear();
 
         mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
                 true, mResultListener::offer);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController).updateSendStatus(eq(SUB_ID),
+                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT), eq(1),
+                eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
 
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController).isPollingInIdleState();
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         verifyNoMoreInteractions(mMockDatagramController);
+        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
+        clearInvocations(mMockSatelliteModemInterface);
+        clearInvocations(mMockDatagramController);
+        mResultListener.clear();
+
+        clearInvocations(mMockSatelliteModemInterface);
+        clearInvocations(mMockDatagramController);
+        mResultListener.clear();
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+        processAllMessages();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
+        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        moveTimeForward(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        processAllMessages();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(1),
+                        eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        assertEquals(1, mResultListener.size());
+        assertThat(mResultListener.peek()).isEqualTo(
+                SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        mResultListener.clear();
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        assertEquals(0, mResultListener.size());
+
+        clearInvocations(mMockSatelliteModemInterface);
+        clearInvocations(mMockDatagramController);
+        mResultListener.clear();
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+        processAllMessages();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
+        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+        assertEquals(0, mResultListener.size());
+
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        processAllMessages();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        assertEquals(1, mResultListener.size());
+        assertThat(mResultListener.peek()).isEqualTo(
+                SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
+        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
     }
 
     @Test
-    public void testSendSatelliteDatagram_usingSatelliteModemInterface_failure() throws  Exception {
-        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+    public void testSendSatelliteDatagram_timeout() throws  Exception {
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[3];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+
+            // DatagramDispatcher should ignore the second EVENT_SEND_SATELLITE_DATAGRAM_DONE
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+
+            return null;
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+        doReturn(false).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected();
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        mContextFixture.putIntResource(
+                R.integer.config_wait_for_datagram_sending_response_timeout_millis,
+                TEST_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMEOUT_MILLIS);
+        mResultListener.clear();
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        verifyNoMoreInteractions(mMockDatagramController);
+        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
+        clearInvocations(mMockSatelliteModemInterface);
+        clearInvocations(mMockDatagramController);
+        mResultListener.clear();
+
+        // No response for the send request from modem
+        doNothing().when(mMockSatelliteModemInterface).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(1),
+                        eq(SATELLITE_RESULT_MODEM_TIMEOUT));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        verifyNoMoreInteractions(mMockDatagramController);
+        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+        verify(mMockSatelliteModemInterface).abortSendingSatelliteDatagrams(any(Message.class));
+        assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_MODEM_TIMEOUT);
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_failure() throws  Exception {
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[3];
 
             mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
                             new AsyncResult(message.obj, null,
                                     new SatelliteManager.SatelliteException(
-                                            SatelliteManager.SATELLITE_SERVICE_ERROR)))
+                                            SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR)))
                     .sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
@@ -173,144 +372,38 @@
 
         processAllMessages();
 
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
         mInOrder.verify(mMockDatagramController).isPollingInIdleState();
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0),
-                        eq(SatelliteManager.SATELLITE_SERVICE_ERROR));
+                        eq(SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR));
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         verifyNoMoreInteractions(mMockDatagramController);
 
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR);
-    }
-
-    @Test
-    public void testSendSatelliteDatagram_usingCommandsInterface_phoneNull() throws Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {null});
-
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
-
-        processAllMessages();
-
-        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0),
-                        eq(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        verifyNoMoreInteractions(mMockDatagramController);
-
-        assertThat(mResultListener.peek())
-                .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-    }
-
-    @Test
-    public void testSendSatelliteDatagram_usingCommandsInterface_success() throws  Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-
-            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
-                            new AsyncResult(message.obj, null, null))
-                    .sendToTarget();
-            return null;
-        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
-                anyBoolean());
-
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram,
-                true, mResultListener::offer);
-
-        processAllMessages();
-
-        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        verifyNoMoreInteractions(mMockDatagramController);
-
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
-    }
-
-    @Test
-    public void testSendSatelliteDatagram_usingCommandsInterface_failure() throws  Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-
-            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
-                            new AsyncResult(message.obj, null,
-                                    new SatelliteManager.SatelliteException(
-                                            SatelliteManager.SATELLITE_SERVICE_ERROR)))
-                    .sendToTarget();
-            return null;
-        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
-                anyBoolean());
-
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
-
-        processAllMessages();
-
-        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0),
-                        eq(SatelliteManager.SATELLITE_SERVICE_ERROR));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        verifyNoMoreInteractions(mMockDatagramController);
-
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR);
+        assertThat(mResultListener.peek()).isEqualTo(
+                SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
     }
 
     @Test
     public void testSendSatelliteDatagram_DemoMode_Align_Success() throws Exception {
-        mTestDemoModeDatagramDispatcher.setDemoMode(true);
-        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(true);
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
         doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-
-            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+            Message message = (Message) invocation.getArguments()[3];
+            mTestDemoModeDatagramDispatcher.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
                             new AsyncResult(message.obj, null, null))
                     .sendToTarget();
             return null;
-        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
-                anyBoolean());
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+        mTestDemoModeDatagramDispatcher.setDemoMode(true);
+        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(true);
 
         mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
                 true, mResultListener::offer);
@@ -320,38 +413,35 @@
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
         mTestDemoModeDatagramDispatcher.setDemoMode(false);
-        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false);
+        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
     }
 
     @Test
     public void testSendSatelliteDatagram_DemoMode_Align_failed() throws Exception {
-        long previousTimer = mTestDemoModeDatagramDispatcher.getSatelliteAlignedTimeoutDuration();
-        mTestDemoModeDatagramDispatcher.setDemoMode(true);
-        mTestDemoModeDatagramDispatcher.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
-        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false);
-
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
         doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-
+            Message message = (Message) invocation.getArguments()[3];
             mTestDemoModeDatagramDispatcher.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
                             new AsyncResult(message.obj, null, null))
                     .sendToTarget();
             return null;
-        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
-                anyBoolean());
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+
+        long previousTimer = mTestDemoModeDatagramDispatcher.getSatelliteAlignedTimeoutDuration();
+        mTestDemoModeDatagramDispatcher.setDemoMode(true);
+        mTestDemoModeDatagramDispatcher.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
+        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
 
         mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
                 true, mResultListener::offer);
@@ -359,36 +449,36 @@
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         processAllFutureMessages();
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
-                        anyInt(), eq(SatelliteManager.SATELLITE_NOT_REACHABLE));
+                        anyInt(), eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_NOT_REACHABLE);
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        assertThat(mResultListener.peek()).isEqualTo(
+                SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
         mTestDemoModeDatagramDispatcher.setDemoMode(false);
-        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false);
+        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
         mTestDemoModeDatagramDispatcher.setDuration(previousTimer);
     }
 
     @Test
     public void testSendSatelliteDatagram_DemoMode_data_type_location_sharing() throws Exception {
-        mTestDemoModeDatagramDispatcher.setDemoMode(true);
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
         doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-
+            Message message = (Message) invocation.getArguments()[3];
             mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
-                            new AsyncResult(message.obj, null, null))
-                    .sendToTarget();
+                    new AsyncResult(message.obj, SatelliteManager.SATELLITE_RESULT_SUCCESS,
+                            null)).sendToTarget();
             return null;
-        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
-                anyBoolean());
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+        mTestDemoModeDatagramDispatcher.setDemoMode(true);
+        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(true);
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
 
         mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram,
                 true, mResultListener::offer);
@@ -398,22 +488,22 @@
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
 
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
 
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
 
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
 
         mTestDemoModeDatagramDispatcher.setDemoMode(false);
-        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false);
+        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
     }
 
     @Test
@@ -424,7 +514,8 @@
                 true, mResultListener::offer);
         processAllMessages();
         // As modem is busy receiving datagrams, sending datagram did not proceed further.
-        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController, times(2)).isPollingInIdleState();
         verifyNoMoreInteractions(mMockDatagramController);
     }
 
@@ -449,11 +540,11 @@
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(anyInt(),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
-                        eq(1), eq(SatelliteManager.SATELLITE_REQUEST_ABORTED));
+                        eq(1), eq(SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED));
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(anyInt(),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
-                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
     }
 
     @Test
@@ -466,7 +557,83 @@
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(anyInt(),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
-                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+    }
+
+    @Test
+    public void testSendSatelliteDatagramToModemInDemoMode()
+            throws Exception {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[3];
+            mTestDemoModeDatagramDispatcher.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+        mTestDemoModeDatagramDispatcher.setDemoMode(true);
+        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(true);
+        mIntegerConsumerSemaphore.drainPermits();
+
+        // Test when overlay config config_send_satellite_datagram_to_modem_in_demo_mode is true
+        mTestDemoModeDatagramDispatcher.setShouldSendDatagramToModemInDemoMode(null);
+        mContextFixture.putBooleanResource(mConfigSendSatelliteDatagramToModemInDemoMode, true);
+        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mIntegerConsumer);
+        processAllMessages();
+        waitForIntegerConsumerResult(1);
+        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
+                (int) mIntegerConsumerResult.get(0));
+        mIntegerConsumerResult.clear();
+        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+
+        // Test when overlay config config_send_satellite_datagram_to_modem_in_demo_mode is false
+        reset(mMockSatelliteModemInterface);
+        mTestDemoModeDatagramDispatcher.setShouldSendDatagramToModemInDemoMode(null);
+        mContextFixture.putBooleanResource(mConfigSendSatelliteDatagramToModemInDemoMode, false);
+        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mIntegerConsumer);
+        processAllMessages();
+        waitForIntegerConsumerResult(1);
+        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
+                (int) mIntegerConsumerResult.get(0));
+        mIntegerConsumerResult.clear();
+        verify(mMockSatelliteModemInterface, never()).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+
+        // Send datagram one more time
+        reset(mMockSatelliteModemInterface);
+        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mIntegerConsumer);
+        processAllMessages();
+        waitForIntegerConsumerResult(1);
+        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
+                (int) mIntegerConsumerResult.get(0));
+        mIntegerConsumerResult.clear();
+        verify(mMockSatelliteModemInterface, never()).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+
+        mTestDemoModeDatagramDispatcher.setDemoMode(false);
+        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
+        mTestDemoModeDatagramDispatcher.setShouldSendDatagramToModemInDemoMode(null);
+    }
+
+    private boolean waitForIntegerConsumerResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mIntegerConsumerSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive IIntegerConsumer() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForIIntegerConsumerResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
     }
 
     private static class TestDatagramDispatcher extends DatagramDispatcher {
@@ -483,8 +650,8 @@
         }
 
         @Override
-        protected  void onDeviceAlignedWithSatellite(boolean isAligned) {
-            super.onDeviceAlignedWithSatellite(isAligned);
+        protected  void setDeviceAlignedWithSatellite(boolean isAligned) {
+            super.setDeviceAlignedWithSatellite(isAligned);
         }
 
         @Override
@@ -492,8 +659,18 @@
             return mLong;
         }
 
+        @Override
+        protected void setShouldSendDatagramToModemInDemoMode(
+                @Nullable Boolean shouldSendToModemInDemoMode) {
+            super.setShouldSendDatagramToModemInDemoMode(shouldSendToModemInDemoMode);
+        }
+
         public void setDuration(long duration) {
             mLong = duration;
         }
     }
+
+    private static void loge(String message) {
+        Rlog.e(TAG, message);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
index 1c3777d..3a42881 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -18,19 +18,16 @@
 
 import static com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT;
 
-import android.annotation.NonNull;
-import android.content.Context;
-import android.provider.Telephony;
-import android.telephony.satellite.ISatelliteDatagramCallback;
-import android.test.mock.MockContentResolver;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.inOrder;
@@ -38,20 +35,27 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
 import android.os.AsyncResult;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.IBinder;
 import android.os.RemoteException;
+import android.provider.Telephony;
+import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
+import android.test.mock.MockContentResolver;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.Pair;
 
 import com.android.internal.telephony.IVoidConsumer;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 
@@ -64,6 +68,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidTestingRunner.class)
@@ -73,6 +78,8 @@
     private static final int SUB_ID = 0;
     private static final String TEST_MESSAGE = "This is a test datagram message";
     private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1);
+    private static final long TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS =
+            TimeUnit.SECONDS.toMillis(60);
 
     private DatagramReceiver mDatagramReceiverUT;
     private DatagramReceiver.SatelliteDatagramListenerHandler mSatelliteDatagramListenerHandler;
@@ -82,6 +89,7 @@
     @Mock private DatagramController mMockDatagramController;
     @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
     @Mock private ControllerMetricsStats mMockControllerMetricsStats;
+    @Mock private SatelliteSessionController mMockSatelliteSessionController;
 
     /** Variables required to receive datagrams in the unit tests. */
     LinkedBlockingQueue<Integer> mResultListener;
@@ -110,6 +118,8 @@
                 mMockSatelliteModemInterface);
         replaceInstance(ControllerMetricsStats.class, "sInstance", null,
                 mMockControllerMetricsStats);
+        replaceInstance(SatelliteSessionController.class, "sInstance", null,
+                mMockSatelliteSessionController);
 
         mDatagramReceiverUT = DatagramReceiver.make(mContext, Looper.myLooper(),
                 mMockDatagramController);
@@ -124,6 +134,7 @@
 
         when(mMockDatagramController.isSendingInIdleState()).thenReturn(true);
         when(mMockDatagramController.isPollingInIdleState()).thenReturn(true);
+        when(mMockDatagramController.needsWaitingForSatelliteConnected()).thenReturn(false);
         processAllMessages();
     }
 
@@ -152,17 +163,66 @@
                     .sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface).pollPendingSatelliteDatagrams(any(Message.class));
+        doReturn(true).when(mMockDatagramController).needsWaitingForSatelliteConnected();
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        mResultListener.clear();
 
         mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController).updateReceiveStatus(eq(SUB_ID),
+                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT), eq(0),
+                eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        assertTrue(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
 
+        doReturn(false).when(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mDatagramReceiverUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        verify(mMockSatelliteModemInterface, times(1))
+                .pollPendingSatelliteDatagrams(any(Message.class));
+        assertEquals(1, mResultListener.size());
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
+        assertFalse(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        clearInvocations(mMockSatelliteModemInterface);
+        mResultListener.clear();
+        doReturn(true).when(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        assertTrue(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        moveTimeForward(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController).updateReceiveStatus(eq(SUB_ID),
+                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED), eq(0),
+                eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
+        mInOrder.verify(mMockDatagramController).updateReceiveStatus(eq(SUB_ID),
+                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        assertEquals(1, mResultListener.size());
+        assertThat(mResultListener.peek()).isEqualTo(
+                SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+        assertFalse(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        mResultListener.clear();
+        mDatagramReceiverUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        assertEquals(0, mResultListener.size());
     }
 
     @Test
@@ -175,7 +235,7 @@
             mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/,
                             new AsyncResult(message.obj, null,
                                     new SatelliteManager.SatelliteException(
-                                            SatelliteManager.SATELLITE_SERVICE_ERROR)))
+                                            SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR)))
                     .sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface).pollPendingSatelliteDatagrams(any(Message.class));
@@ -187,98 +247,14 @@
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
-                        eq(0), eq(SatelliteManager.SATELLITE_SERVICE_ERROR));
+                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR));
 
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR);
-    }
-
-    @Test
-    public void testPollPendingSatelliteDatagrams_usingCommandsInterface_phoneNull()
-            throws Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {null});
-
-        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
-
-        processAllMessages();
-
-        mInOrder.verify(mMockDatagramController)
-                .updateReceiveStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        mInOrder.verify(mMockDatagramController)
-                .updateReceiveStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
-                        eq(0), eq(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
-        mInOrder.verify(mMockDatagramController)
-                .updateReceiveStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
-                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
-
-        assertThat(mResultListener.peek())
-                .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-    }
-
-    @Test
-    public void testPollPendingSatelliteDatagrams_usingCommandsInterface_success()
-            throws Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-
-            mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/,
-                            new AsyncResult(message.obj, null, null))
-                    .sendToTarget();
-            return null;
-        }).when(mPhone).pollPendingSatelliteDatagrams(any(Message.class));
-
-        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
-
-        processAllMessages();
-
-        mInOrder.verify(mMockDatagramController)
-                .updateReceiveStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
-    }
-
-    @Test
-    public void testPollPendingSatelliteDatagrams_usingCommandsInterface_failure()
-            throws Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-
-            mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/,
-                            new AsyncResult(message.obj, null,
-                                    new SatelliteManager.SatelliteException(
-                                            SatelliteManager.SATELLITE_SERVICE_ERROR)))
-                    .sendToTarget();
-            return null;
-        }).when(mPhone).pollPendingSatelliteDatagrams(any(Message.class));
-
-        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
-
-        processAllMessages();
-
-        mInOrder.verify(mMockDatagramController)
-                .updateReceiveStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
-        mInOrder.verify(mMockDatagramController)
-                .updateReceiveStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
-                        eq(0), eq(SatelliteManager.SATELLITE_SERVICE_ERROR));
-
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR);
+        assertThat(mResultListener.peek()).isEqualTo(
+                SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
     }
 
     @Test
@@ -292,29 +268,40 @@
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE),
-                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
-                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
     }
 
     @Test
     public void testSatelliteDatagramReceived_success_zeroPendingCount() {
+        TestSatelliteDatagramCallback testSatelliteDatagramCallback =
+                new TestSatelliteDatagramCallback();
+
+        mSatelliteDatagramListenerHandler.addListener(testSatelliteDatagramCallback);
         mSatelliteDatagramListenerHandler.obtainMessage(1 /*EVENT_SATELLITE_DATAGRAM_RECEIVED*/,
                         new AsyncResult(null, new Pair<>(mDatagram, 0), null))
                 .sendToTarget();
-
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS),
-                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
-                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        assertTrue(testSatelliteDatagramCallback.waitForOnSatelliteDatagramReceived());
+
+        assertTrue(testSatelliteDatagramCallback.sendInternalAck());
+        try {
+            processAllFutureMessages();
+        } catch (Exception e) {
+            fail("Unexpected exception e=" + e);
+        }
     }
 
     @Test
@@ -328,14 +315,14 @@
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS),
-                        eq(10), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(10), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
     }
 
     @Test
     public void testPollPendingSatelliteDatagrams_DemoMode_Align_succeed() throws Exception {
         // Checks invalid case only as SatelliteController does not exist in unit test
         mTestDemoModeDatagramReceiver.setDemoMode(true);
-        mTestDemoModeDatagramReceiver.onDeviceAlignedWithSatellite(true);
+        mTestDemoModeDatagramReceiver.setDeviceAlignedWithSatellite(true);
         when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram);
         mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
@@ -344,19 +331,19 @@
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
                         anyInt(),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
                         anyInt(),
-                        eq(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
+                        eq(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE));
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         anyInt(),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         assertThat(mResultListener.peek())
-                .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                .isEqualTo(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
     }
 
     @Test
@@ -365,7 +352,7 @@
         long previousTimer = mTestDemoModeDatagramReceiver.getSatelliteAlignedTimeoutDuration();
         mTestDemoModeDatagramReceiver.setDemoMode(true);
         mTestDemoModeDatagramReceiver.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
-        mTestDemoModeDatagramReceiver.onDeviceAlignedWithSatellite(false);
+        mTestDemoModeDatagramReceiver.setDeviceAlignedWithSatellite(false);
         when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram);
         mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
@@ -374,23 +361,23 @@
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
                         anyInt(),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         processAllFutureMessages();
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
                         anyInt(),
-                        eq(SatelliteManager.SATELLITE_NOT_REACHABLE));
+                        eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         anyInt(),
-                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         assertThat(mResultListener.peek())
-                .isEqualTo(SatelliteManager.SATELLITE_NOT_REACHABLE);
+                .isEqualTo(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
 
         mTestDemoModeDatagramReceiver.setDemoMode(false);
-        mTestDemoModeDatagramReceiver.onDeviceAlignedWithSatellite(false);
+        mTestDemoModeDatagramReceiver.setDeviceAlignedWithSatellite(false);
         mTestDemoModeDatagramReceiver.setDuration(previousTimer);
     }
 
@@ -400,7 +387,7 @@
 
         mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_MODEM_BUSY);
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_MODEM_BUSY);
     }
 
     @Test
@@ -410,7 +397,7 @@
 
         mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_MODEM_BUSY);
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_MODEM_BUSY);
     }
 
     @Test
@@ -434,11 +421,11 @@
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(anyInt(),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
-                        eq(10), eq(SatelliteManager.SATELLITE_REQUEST_ABORTED));
+                        eq(10), eq(SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED));
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(anyInt(),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
-                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
     }
 
     @Test
@@ -453,12 +440,12 @@
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(anyInt(),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
-                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
     }
 
     @Test
     public void testRegisterForSatelliteDatagram_satelliteNotSupported() {
-        when(mMockSatelliteController.isSatelliteSupported()).thenReturn(false);
+        when(mMockSatelliteController.isSatelliteSupportedViaOem()).thenReturn(false);
 
         ISatelliteDatagramCallback callback = new ISatelliteDatagramCallback() {
             @Override
@@ -474,7 +461,7 @@
         };
 
         assertThat(mDatagramReceiverUT.registerForSatelliteDatagram(SUB_ID, callback))
-                .isEqualTo(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+                .isEqualTo(SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED);
     }
 
     private static class TestDatagramReceiver extends DatagramReceiver {
@@ -491,8 +478,8 @@
         }
 
         @Override
-        protected void onDeviceAlignedWithSatellite(boolean isAligned) {
-            super.onDeviceAlignedWithSatellite(isAligned);
+        protected void setDeviceAlignedWithSatellite(boolean isAligned) {
+            super.setDeviceAlignedWithSatellite(isAligned);
         }
 
         @Override
@@ -504,4 +491,57 @@
             mLong = duration;
         }
     }
+
+    private static class TestSatelliteDatagramCallback extends ISatelliteDatagramCallback.Stub {
+        @Nullable private IVoidConsumer mInternalAck;
+        private final Semaphore mSemaphore = new Semaphore(0);
+
+        @Override
+        public void onSatelliteDatagramReceived(long datagramId,
+                @NonNull SatelliteDatagram datagram, int pendingCount,
+                @NonNull IVoidConsumer internalAck) {
+            logd("onSatelliteDatagramReceived");
+            mInternalAck = internalAck;
+            try {
+                internalAck.accept();
+            } catch (RemoteException e) {
+                logd("onSatelliteDatagramReceived: accept e=" + e);
+                return;
+            }
+
+            try {
+                mSemaphore.release();
+            } catch (Exception e) {
+                logd("onSatelliteDatagramReceived: release e=" + e);
+            }
+        }
+
+        public boolean waitForOnSatelliteDatagramReceived() {
+            logd("waitForOnSatelliteDatagramReceived");
+            try {
+                if (!mSemaphore.tryAcquire(1000, TimeUnit.MILLISECONDS)) {
+                    logd("Timed out to receive onSatelliteDatagramReceived");
+                    return false;
+                }
+            } catch (InterruptedException e) {
+                logd("waitForOnSatelliteDatagramReceived: e=" + e);
+                return false;
+            }
+            return true;
+        }
+
+        public boolean sendInternalAck() {
+            if (mInternalAck == null) {
+                logd("sendInternalAck: mInternalAck is null");
+                return false;
+            }
+            try {
+                mInternalAck.accept();
+            } catch (RemoteException e) {
+                logd("sendInternalAck: accept e=" + e);
+                return false;
+            }
+            return true;
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
index c202f0c..f8827be 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 
 import android.annotation.NonNull;
@@ -75,7 +76,7 @@
         replaceInstance(SatelliteController.class, "sInstance", null,
                 mMockSatelliteController);
         doReturn(Arrays.asList(SATELLITE_PLMN_ARRAY))
-                .when(mMockSatelliteController).getSatellitePlmnList();
+                .when(mMockSatelliteController).getSatellitePlmnsForCarrier(anyInt());
         doReturn(mSatelliteSupportedServiceList).when(mMockSatelliteController)
                 .getSupportedSatelliteServices(SUB_ID, SATELLITE_PLMN);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
index 4587b7c..3917a32 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
@@ -16,10 +16,7 @@
 
 package com.android.internal.telephony.satellite;
 
-import android.os.Bundle;
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -27,20 +24,27 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
 import android.os.Message;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.PointingInfo;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteManager.SatelliteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -75,9 +79,11 @@
 
     private PointingAppController mPointingAppController;
     InOrder mInOrder;
+    InOrder mInOrderForPointingUi;
 
     @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
     @Mock private SatelliteController mMockSatelliteController;
+    @Mock private PackageManager mPackageManager;
 
     private TestSatelliteTransmissionUpdateCallback mSatelliteTransmissionUpdateCallback;
     private TestSatelliteControllerHandler mTestSatelliteControllerHandler;
@@ -92,7 +98,7 @@
         super.setUp(getClass().getSimpleName());
         MockitoAnnotations.initMocks(this);
         logd(TAG + " Setup!");
-
+        mInOrderForPointingUi = inOrder(mContext);
         replaceInstance(SatelliteModemInterface.class, "sInstance", null,
                 mMockSatelliteModemInterface);
         replaceInstance(SatelliteController.class, "sInstance", null,
@@ -111,7 +117,7 @@
     public void tearDown() throws Exception {
         logd(TAG + " tearDown");
         mResultListener = null;
-
+        mInOrderForPointingUi = null;
         mSatelliteTransmissionUpdateCallback = null;
         super.tearDown();
     }
@@ -237,170 +243,64 @@
     }
 
     private void setUpResponseForStartTransmissionUpdates(
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SatelliteManager.SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SatelliteManager.SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
             AsyncResult.forMessage(message, null, exception);
             message.sendToTarget();
             return null;
-        }).when(mPhone).startSatellitePositionUpdates(any(Message.class));
-
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-            AsyncResult.forMessage(message, null, exception);
-            message.sendToTarget();
-            return null;
         }).when(mMockSatelliteModemInterface).startSendingSatellitePointingInfo(any(Message.class));
     }
 
     private void setUpResponseForStopTransmissionUpdates(
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SatelliteManager.SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SatelliteManager.SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
             AsyncResult.forMessage(message, null, exception);
             message.sendToTarget();
             return null;
-        }).when(mPhone).stopSatellitePositionUpdates(any(Message.class));
-
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-            AsyncResult.forMessage(message, null, exception);
-            message.sendToTarget();
-            return null;
         }).when(mMockSatelliteModemInterface).stopSendingSatellitePointingInfo(any(Message.class));
     }
 
     @Test
-    public void testStartSatelliteTransmissionUpdates_CommandInterface()
-            throws Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        Message testMessage = mTestSatelliteControllerHandler
-                .obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
-        setUpResponseForStartTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE);
-        mPointingAppController.startSatelliteTransmissionUpdates(testMessage, mPhone);
-
-        processAllMessages();
-
-        verify(mMockSatelliteModemInterface, never())
-                .startSendingSatellitePointingInfo(eq(testMessage));
-
-        verify(mPhone)
-                .startSatellitePositionUpdates(eq(testMessage));
-
-        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode);
-
-        assertTrue(mPointingAppController.getStartedSatelliteTransmissionUpdates());
-    }
-
-    @Test
     public void testStartSatelliteTransmissionUpdates_success()
             throws Exception {
-        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         mPointingAppController.setStartedSatelliteTransmissionUpdates(false);
         Message testMessage = mTestSatelliteControllerHandler
                 .obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
-        setUpResponseForStartTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE);
-        mPointingAppController.startSatelliteTransmissionUpdates(testMessage, mPhone);
+        setUpResponseForStartTransmissionUpdates(SatelliteManager.SATELLITE_RESULT_SUCCESS);
+        mPointingAppController.startSatelliteTransmissionUpdates(testMessage);
 
         verify(mMockSatelliteModemInterface)
                 .startSendingSatellitePointingInfo(eq(testMessage));
 
-        verify(mPhone, never())
-                .startSatellitePositionUpdates(eq(testMessage));
-
         processAllMessages();
 
 
         assertTrue(mPointingAppController.getStartedSatelliteTransmissionUpdates());
-        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode);
-    }
-
-    @Test
-    public void testStartSatelliteTransmissionUpdates_phoneNull()
-            throws Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        mPointingAppController.setStartedSatelliteTransmissionUpdates(false);
-        Message testMessage = mTestSatelliteControllerHandler
-                .obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
-
-        mPointingAppController.startSatelliteTransmissionUpdates(testMessage, null);
-        processAllMessages();
-        verify(mMockSatelliteModemInterface, never())
-                .startSendingSatellitePointingInfo(eq(testMessage));
-
-        verify(mPhone, never())
-                .startSatellitePositionUpdates(eq(testMessage));
-
-        assertFalse(mPointingAppController.getStartedSatelliteTransmissionUpdates());
-
-        assertEquals(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, mResultCode);
-    }
-
-    @Test
-    public void testStopSatelliteTransmissionUpdates_CommandInterface()
-            throws Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        setUpResponseForStopTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE);
-        Message testMessage = mTestSatelliteControllerHandler
-                .obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
-        mPointingAppController.stopSatelliteTransmissionUpdates(testMessage, mPhone);
-
-        processAllMessages();
-
-        verify(mMockSatelliteModemInterface, never())
-                .stopSendingSatellitePointingInfo(eq(testMessage));
-
-        verify(mPhone)
-                .stopSatellitePositionUpdates(eq(testMessage));
-
-        assertFalse(mPointingAppController.getStartedSatelliteTransmissionUpdates());
-
-        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode);
+        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS, mResultCode);
     }
 
     @Test
     public void testStopSatelliteTransmissionUpdates_success()
             throws Exception {
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        setUpResponseForStopTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE);
+        setUpResponseForStopTransmissionUpdates(SatelliteManager.SATELLITE_RESULT_SUCCESS);
         Message testMessage = mTestSatelliteControllerHandler
                 .obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
-        mPointingAppController.stopSatelliteTransmissionUpdates(testMessage, mPhone);
+        mPointingAppController.stopSatelliteTransmissionUpdates(testMessage);
 
         processAllMessages();
 
         verify(mMockSatelliteModemInterface)
                 .stopSendingSatellitePointingInfo(eq(testMessage));
 
-        verify(mPhone, never())
-                .stopSatellitePositionUpdates(eq(testMessage));
-
         assertFalse(mPointingAppController.getStartedSatelliteTransmissionUpdates());
-        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode);
-    }
-
-    @Test
-    public void testStopSatellitePointingInfo_phoneNull()
-            throws Exception {
-        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        Message testMessage = mTestSatelliteControllerHandler
-                .obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, null);
-        mPointingAppController.stopSatelliteTransmissionUpdates(testMessage, null);
-
-        processAllMessages();
-
-        verify(mMockSatelliteModemInterface, never())
-                .stopSendingSatellitePointingInfo(eq(testMessage));
-
-        verify(mPhone, never())
-                .stopSatellitePositionUpdates(eq(testMessage));
-
-        assertEquals(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, mResultCode);
-
+        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS, mResultCode);
     }
 
     @Test
@@ -417,95 +317,70 @@
     }
 
     @Test
+    public void testRestartPointingUi() throws Exception {
+        mPointingAppController.startPointingUI(true);
+        mInOrderForPointingUi.verify(mContext).startActivity(any(Intent.class));
+        testRestartPointingUi(true);
+        mPointingAppController.startPointingUI(false);
+        mInOrderForPointingUi.verify(mContext).startActivity(any(Intent.class));
+        testRestartPointingUi(false);
+    }
+
+    private void testRestartPointingUi(boolean expectedFullScreen) {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doReturn(new String[]{KEY_POINTING_UI_PACKAGE_NAME}).when(mPackageManager)
+            .getPackagesForUid(anyInt());
+        mPointingAppController.mUidImportanceListener.onUidImportance(1, IMPORTANCE_GONE);
+        ArgumentCaptor<Intent> restartedIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        mInOrderForPointingUi.verify(mContext).startActivity(restartedIntentCaptor.capture());
+        Intent restartIntent = restartedIntentCaptor.getValue();
+        assertEquals(KEY_POINTING_UI_PACKAGE_NAME, restartIntent.getComponent().getPackageName());
+        assertEquals(KEY_POINTING_UI_CLASS_NAME, restartIntent.getComponent().getClassName());
+        Bundle b = restartIntent.getExtras();
+        assertTrue(b.containsKey(KEY_NEED_FULL_SCREEN));
+        // Checking if last value of KEY_NEED_FULL_SCREEN is taken or not
+        assertEquals(expectedFullScreen, b.getBoolean(KEY_NEED_FULL_SCREEN));
+    }
+
+    @Test
     public void testUpdateSendDatagramTransferState() throws Exception {
         mPointingAppController.registerForSatelliteTransmissionUpdates(SUB_ID,
-                mSatelliteTransmissionUpdateCallback, mPhone);
+                mSatelliteTransmissionUpdateCallback);
         mPointingAppController.updateSendDatagramTransferState(SUB_ID,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS, 1,
-                SatelliteManager.SATELLITE_ERROR_NONE);
+                SatelliteManager.SATELLITE_RESULT_SUCCESS);
         assertTrue(waitForSendDatagramStateChangedRessult(1));
         assertEquals(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
                 mSatelliteTransmissionUpdateCallback.getState());
         assertEquals(1, mSatelliteTransmissionUpdateCallback.getSendPendingCount());
-        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE,
+        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
                 mSatelliteTransmissionUpdateCallback.getErrorCode());
         assertTrue(mSatelliteTransmissionUpdateCallback.inSendDatagramStateCallback);
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(SUB_ID,
-                    mResultListener::offer, mSatelliteTransmissionUpdateCallback, mPhone);
+                    mResultListener::offer, mSatelliteTransmissionUpdateCallback);
         mResultListener.clear();
     }
 
     @Test
     public void testUpdateReceiveDatagramTransferState() throws Exception {
         mPointingAppController.registerForSatelliteTransmissionUpdates(SUB_ID,
-                mSatelliteTransmissionUpdateCallback, mPhone);
+                mSatelliteTransmissionUpdateCallback);
         mPointingAppController.updateReceiveDatagramTransferState(SUB_ID,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS, 2,
-                SatelliteManager.SATELLITE_ERROR_NONE);
+                SatelliteManager.SATELLITE_RESULT_SUCCESS);
         assertTrue(waitForReceiveDatagramStateChangedRessult(1));
         assertEquals(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS,
                 mSatelliteTransmissionUpdateCallback.getState());
         assertEquals(2, mSatelliteTransmissionUpdateCallback.getReceivePendingCount());
-        assertEquals(SatelliteManager.SATELLITE_ERROR_NONE,
+        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
                 mSatelliteTransmissionUpdateCallback.getErrorCode());
         assertTrue(mSatelliteTransmissionUpdateCallback.inReceiveDatagramStateCallback);
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(SUB_ID,
-                    mResultListener::offer, mSatelliteTransmissionUpdateCallback, mPhone);
+                    mResultListener::offer, mSatelliteTransmissionUpdateCallback);
         mResultListener.clear();
     }
 
     @Test
-    public void testRegisterForSatelliteTransmissionUpdates_CommandInterface() throws Exception {
-        mResultListener.clear();
-        mInOrder = inOrder(mPhone);
-        TestSatelliteTransmissionUpdateCallback callback1 = new
-                TestSatelliteTransmissionUpdateCallback();
-        TestSatelliteTransmissionUpdateCallback callback2 = new
-                TestSatelliteTransmissionUpdateCallback();
-        int subId1 = 1;
-        int subId2 = 2;
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1,
-                callback1, mPhone);
-        mInOrder.verify(mPhone).registerForSatellitePositionInfoChanged(any(),
-                eq(1), eq(null));
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1,
-                callback2, mPhone);
-        mInOrder.verify(mPhone, never()).registerForSatellitePositionInfoChanged(any(),
-                eq(1), eq(null));
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2,
-                callback1, mPhone);
-        mInOrder.verify(mPhone).registerForSatellitePositionInfoChanged(any(),
-                eq(1), eq(null));
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2,
-                callback2, mPhone);
-        mInOrder.verify(mPhone, never()).registerForSatellitePositionInfoChanged(any(),
-                eq(1), eq(null));
-        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
-                mResultListener::offer, callback1, mPhone);
-        processAllMessages();
-        //since there are 2 callbacks registered for this sub_id, Handler is not unregistered
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
-        mResultListener.remove();
-        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
-                mResultListener::offer, callback2, mPhone);
-        mInOrder.verify(mPhone).unregisterForSatellitePositionInfoChanged(any(Handler.class));
-        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
-                mResultListener::offer, callback1, mPhone);
-        processAllMessages();
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
-        mResultListener.remove();
-        mInOrder.verify(mPhone, never()).unregisterForSatellitePositionInfoChanged(
-                any(Handler.class));
-        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
-                mResultListener::offer, callback2, null);
-        processAllMessages();
-        assertThat(mResultListener.peek())
-                .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
-        mResultListener.remove();
-        mInOrder = null;
-    }
-
-    @Test
     public void testRegisterForSatelliteTransmissionUpdates() throws Exception {
         mResultListener.clear();
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
@@ -516,53 +391,49 @@
                 TestSatelliteTransmissionUpdateCallback();
         int subId1 = 3;
         int subId2 = 4;
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1,
-                callback1, mPhone);
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1, callback1);
         mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(),
                 eq(1), eq(null));
         mInOrder.verify(mMockSatelliteModemInterface).registerForDatagramTransferStateChanged(any(),
                 eq(4), eq(null));
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1,
-                callback2, mPhone);
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId1, callback2);
         mInOrder.verify(mMockSatelliteModemInterface, never())
                 .registerForSatellitePositionInfoChanged(any(), eq(1), eq(null));
         mInOrder.verify(mMockSatelliteModemInterface, never())
                 .registerForDatagramTransferStateChanged(any(), eq(4), eq(null));
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2,
-                callback1, mPhone);
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, callback1);
         mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(),
                 eq(1), eq(null));
         mInOrder.verify(mMockSatelliteModemInterface).registerForDatagramTransferStateChanged(any(),
                 eq(4), eq(null));
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2,
-                callback2, mPhone);
+        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, callback2);
         mInOrder.verify(mMockSatelliteModemInterface, never())
                 .registerForSatellitePositionInfoChanged(any(), eq(1), eq(null));
         mInOrder.verify(mMockSatelliteModemInterface, never())
                 .registerForDatagramTransferStateChanged(any(), eq(4), eq(null));
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
-                mResultListener::offer, callback1, mPhone);
+                mResultListener::offer, callback1);
         processAllMessages();
         //since there are 2 callbacks registered for this sub_id, Handler is not unregistered
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
         mResultListener.remove();
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
-                mResultListener::offer, callback2, mPhone);
+                mResultListener::offer, callback2);
         mInOrder.verify(mMockSatelliteModemInterface).unregisterForSatellitePositionInfoChanged(
                 any(Handler.class));
         mInOrder.verify(mMockSatelliteModemInterface).unregisterForDatagramTransferStateChanged(
                 any(Handler.class));
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
-                mResultListener::offer, callback1, mPhone);
+                mResultListener::offer, callback1);
         processAllMessages();
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
         mResultListener.remove();
         mInOrder.verify(mMockSatelliteModemInterface, never())
                 .unregisterForSatellitePositionInfoChanged(any(Handler.class));
         mInOrder.verify(mMockSatelliteModemInterface, never())
                 .unregisterForDatagramTransferStateChanged(any(Handler.class));
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
-                mResultListener::offer, callback2, null);
+                mResultListener::offer, callback2);
         processAllMessages();
         mInOrder.verify(mMockSatelliteModemInterface).unregisterForSatellitePositionInfoChanged(
                 any(Handler.class));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
index edfd610..431b4cc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -16,7 +16,15 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT;
+import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
+import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GOOD;
+import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GREAT;
+import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
+import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_POOR;
 import static android.telephony.satellite.SatelliteManager.KEY_DEMO_MODE_ENABLED;
+import static android.telephony.satellite.SatelliteManager.KEY_NTN_SIGNAL_STRENGTH;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_CAPABILITIES;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_ENABLED;
@@ -24,22 +32,30 @@
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_PROVISIONED;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_EMTC_NTN;
+import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN;
 import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN;
 import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_PROPRIETARY;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_ERROR;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_ERROR_NONE;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_INVALID_ARGUMENTS;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_INVALID_MODEM_STATE;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_NOT_AUTHORIZED;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_NOT_SUPPORTED;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_NO_RESOURCES;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_REQUEST_IN_PROGRESS;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_AUTHORIZED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NO_RESOURCES;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_FALSE;
 import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_TRUE;
@@ -48,10 +64,12 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.anyVararg;
 import static org.mockito.ArgumentMatchers.eq;
@@ -60,11 +78,14 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.AsyncResult;
@@ -75,13 +96,18 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.satellite.INtnSignalStrengthCallback;
+import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
-import android.telephony.satellite.ISatelliteStateCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
@@ -89,12 +115,16 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.R;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.IVoidConsumer;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
@@ -104,7 +134,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
@@ -123,19 +155,26 @@
 public class SatelliteControllerTest extends TelephonyTest {
     private static final String TAG = "SatelliteControllerTest";
 
-    private static final int EVENT_DEVICE_CONFIG_CHANGED = 29;
-
     private static final long TIMEOUT = 500;
     private static final int SUB_ID = 0;
     private static final int SUB_ID1 = 1;
     private static final int MAX_BYTES_PER_OUT_GOING_DATAGRAM = 339;
     private static final String TEST_SATELLITE_TOKEN = "TEST_SATELLITE_TOKEN";
     private static final String TEST_NEXT_SATELLITE_TOKEN = "TEST_NEXT_SATELLITE_TOKEN";
-    private static final String[] EMPTY_SATELLITE_SERVICES_SUPPORTED_BY_PROVIDERS_STRING_ARRAY = {};
+    private static final String[] EMPTY_STRING_ARRAY = {};
+    private static final List<String> EMPTY_STRING_LIST = new ArrayList<>();
+    private static final String SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY =
+            "satellite_system_notification_done_key";
     private static final int[] ACTIVE_SUB_IDS = {SUB_ID};
+    private static final int TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS =
+            (int) TimeUnit.SECONDS.toMillis(60);
+    private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
+            mCarrierConfigChangedListenerList = new ArrayList<>();
 
     private TestSatelliteController mSatelliteControllerUT;
     private TestSharedPreferences mSharedPreferences;
+    private PersistableBundle mCarrierConfigBundle;
+    private ServiceState mServiceState2;
 
     @Mock private DatagramController mMockDatagramController;
     @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
@@ -145,9 +184,11 @@
     @Mock private ProvisionMetricsStats mMockProvisionMetricsStats;
     @Mock private SessionMetricsStats mMockSessionMetricsStats;
     @Mock private SubscriptionManagerService mMockSubscriptionManagerService;
+    @Mock private NotificationManager mMockNotificationManager;
     private List<Integer> mIIntegerConsumerResults =  new ArrayList<>();
     @Mock private ISatelliteTransmissionUpdateCallback mStartTransmissionUpdateCallback;
     @Mock private ISatelliteTransmissionUpdateCallback mStopTransmissionUpdateCallback;
+    @Mock private FeatureFlags mFeatureFlags;
     private Semaphore mIIntegerConsumerSemaphore = new Semaphore(0);
     private IIntegerConsumer mIIntegerConsumer = new IIntegerConsumer.Stub() {
         @Override
@@ -167,18 +208,23 @@
     private Set<Integer> mSupportedRadioTechnologies = new HashSet<>(Arrays.asList(
             NT_RADIO_TECHNOLOGY_NR_NTN,
             NT_RADIO_TECHNOLOGY_EMTC_NTN,
+            NT_RADIO_TECHNOLOGY_NB_IOT_NTN,
             NT_RADIO_TECHNOLOGY_PROPRIETARY));
     private SatelliteCapabilities mSatelliteCapabilities = new SatelliteCapabilities(
             mSupportedRadioTechnologies, mIsPointingRequired, MAX_BYTES_PER_OUT_GOING_DATAGRAM,
             new HashMap<>());
+    private SatelliteCapabilities mEmptySatelliteCapabilities = new SatelliteCapabilities(
+            new HashSet<>(), mIsPointingRequired, MAX_BYTES_PER_OUT_GOING_DATAGRAM,
+            new HashMap<>());
     private Semaphore mSatelliteCapabilitiesSemaphore = new Semaphore(0);
     private SatelliteCapabilities mQueriedSatelliteCapabilities = null;
-    private int mQueriedSatelliteCapabilitiesResultCode = SATELLITE_ERROR_NONE;
+    private int mQueriedSatelliteCapabilitiesResultCode = SATELLITE_RESULT_SUCCESS;
     private ResultReceiver mSatelliteCapabilitiesReceiver = new ResultReceiver(null) {
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedSatelliteCapabilitiesResultCode = resultCode;
-            if (resultCode == SATELLITE_ERROR_NONE) {
+            logd("mSatelliteCapabilitiesReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_CAPABILITIES)) {
                     mQueriedSatelliteCapabilities = resultData.getParcelable(
                             KEY_SATELLITE_CAPABILITIES, SatelliteCapabilities.class);
@@ -187,25 +233,26 @@
                     mQueriedSatelliteCapabilities = null;
                 }
             } else {
-                logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
                 mQueriedSatelliteCapabilities = null;
             }
             try {
                 mSatelliteCapabilitiesSemaphore.release();
             } catch (Exception ex) {
-                loge("mSatelliteSupportReceiver: Got exception in releasing semaphore, ex=" + ex);
+                loge("mSatelliteCapabilitiesReceiver: Got exception in releasing semaphore, ex="
+                        + ex);
             }
         }
     };
 
     private boolean mQueriedSatelliteSupported = false;
-    private int mQueriedSatelliteSupportedResultCode = SATELLITE_ERROR_NONE;
+    private int mQueriedSatelliteSupportedResultCode = SATELLITE_RESULT_SUCCESS;
     private Semaphore mSatelliteSupportSemaphore = new Semaphore(0);
     private ResultReceiver mSatelliteSupportReceiver = new ResultReceiver(null) {
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedSatelliteSupportedResultCode = resultCode;
-            if (resultCode == SATELLITE_ERROR_NONE) {
+            logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_SUPPORTED)) {
                     mQueriedSatelliteSupported = resultData.getBoolean(KEY_SATELLITE_SUPPORTED);
                 } else {
@@ -213,7 +260,6 @@
                     mQueriedSatelliteSupported = false;
                 }
             } else {
-                logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
                 mQueriedSatelliteSupported = false;
             }
             try {
@@ -225,14 +271,14 @@
     };
 
     private boolean mQueriedIsSatelliteEnabled = false;
-    private int mQueriedIsSatelliteEnabledResultCode = SATELLITE_ERROR_NONE;
+    private int mQueriedIsSatelliteEnabledResultCode = SATELLITE_RESULT_SUCCESS;
     private Semaphore mIsSatelliteEnabledSemaphore = new Semaphore(0);
     private ResultReceiver mIsSatelliteEnabledReceiver = new ResultReceiver(null) {
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             logd("mIsSatelliteEnabledReceiver: resultCode=" + resultCode);
             mQueriedIsSatelliteEnabledResultCode = resultCode;
-            if (resultCode == SATELLITE_ERROR_NONE) {
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_ENABLED)) {
                     mQueriedIsSatelliteEnabled = resultData.getBoolean(KEY_SATELLITE_ENABLED);
                 } else {
@@ -245,19 +291,20 @@
             try {
                 mIsSatelliteEnabledSemaphore.release();
             } catch (Exception ex) {
-                loge("mIsSatelliteEnableReceiver: Got exception in releasing semaphore, ex=" + ex);
+                loge("mIsSatelliteEnabledReceiver: Got exception in releasing semaphore, ex=" + ex);
             }
         }
     };
 
     private boolean mQueriedIsDemoModeEnabled = false;
-    private int mQueriedIsDemoModeEnabledResultCode = SATELLITE_ERROR_NONE;
+    private int mQueriedIsDemoModeEnabledResultCode = SATELLITE_RESULT_SUCCESS;
     private Semaphore mIsDemoModeEnabledSemaphore = new Semaphore(0);
     private ResultReceiver mIsDemoModeEnabledReceiver = new ResultReceiver(null) {
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedIsDemoModeEnabledResultCode = resultCode;
-            if (resultCode == SATELLITE_ERROR_NONE) {
+            logd("mIsDemoModeEnabledReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_DEMO_MODE_ENABLED)) {
                     mQueriedIsDemoModeEnabled = resultData.getBoolean(KEY_DEMO_MODE_ENABLED);
                 } else {
@@ -265,7 +312,6 @@
                     mQueriedIsDemoModeEnabled = false;
                 }
             } else {
-                logd("mIsSatelliteEnableReceiver: resultCode=" + resultCode);
                 mQueriedIsDemoModeEnabled = false;
             }
             try {
@@ -277,13 +323,14 @@
     };
 
     private boolean mQueriedIsSatelliteProvisioned = false;
-    private int mQueriedIsSatelliteProvisionedResultCode = SATELLITE_ERROR_NONE;
+    private int mQueriedIsSatelliteProvisionedResultCode = SATELLITE_RESULT_SUCCESS;
     private Semaphore mIsSatelliteProvisionedSemaphore = new Semaphore(0);
     private ResultReceiver mIsSatelliteProvisionedReceiver = new ResultReceiver(null) {
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedIsSatelliteProvisionedResultCode = resultCode;
-            if (resultCode == SATELLITE_ERROR_NONE) {
+            logd("mIsSatelliteProvisionedReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) {
                     mQueriedIsSatelliteProvisioned =
                             resultData.getBoolean(KEY_SATELLITE_PROVISIONED);
@@ -304,13 +351,14 @@
     };
 
     private boolean mQueriedSatelliteAllowed = false;
-    private int mQueriedSatelliteAllowedResultCode = SATELLITE_ERROR_NONE;
+    private int mQueriedSatelliteAllowedResultCode = SATELLITE_RESULT_SUCCESS;
     private Semaphore mSatelliteAllowedSemaphore = new Semaphore(0);
     private ResultReceiver mSatelliteAllowedReceiver = new ResultReceiver(null) {
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedSatelliteAllowedResultCode = resultCode;
-            if (resultCode == SATELLITE_ERROR_NONE) {
+            logd("mSatelliteAllowedReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
                     mQueriedSatelliteAllowed = resultData.getBoolean(
                             KEY_SATELLITE_COMMUNICATION_ALLOWED);
@@ -319,7 +367,6 @@
                     mQueriedSatelliteAllowed = false;
                 }
             } else {
-                logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
                 mQueriedSatelliteAllowed = false;
             }
             try {
@@ -332,13 +379,14 @@
 
     private int mQueriedSatelliteVisibilityTime = -1;
     private int mSatelliteNextVisibilityTime = 3600;
-    private int mQueriedSatelliteVisibilityTimeResultCode = SATELLITE_ERROR_NONE;
+    private int mQueriedSatelliteVisibilityTimeResultCode = SATELLITE_RESULT_SUCCESS;
     private Semaphore mSatelliteVisibilityTimeSemaphore = new Semaphore(0);
     private ResultReceiver mSatelliteVisibilityTimeReceiver = new ResultReceiver(null) {
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedSatelliteVisibilityTimeResultCode = resultCode;
-            if (resultCode == SATELLITE_ERROR_NONE) {
+            logd("mSatelliteVisibilityTimeReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_NEXT_VISIBILITY)) {
                     mQueriedSatelliteVisibilityTime = resultData.getInt(
                             KEY_SATELLITE_NEXT_VISIBILITY);
@@ -347,20 +395,46 @@
                     mQueriedSatelliteVisibilityTime = -1;
                 }
             } else {
-                logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
                 mQueriedSatelliteVisibilityTime = -1;
             }
             try {
                 mSatelliteVisibilityTimeSemaphore.release();
             } catch (Exception ex) {
-                loge("mSatelliteAllowedReceiver: Got exception in releasing semaphore, ex=" + ex);
+                loge("mSatelliteVisibilityTimeReceiver: Got exception in releasing semaphore, ex="
+                        + ex);
             }
         }
     };
 
-    private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
-            mCarrierConfigChangedListenerList = new ArrayList<>();
-    private PersistableBundle mCarrierConfigBundle;
+    private @NtnSignalStrength.NtnSignalStrengthLevel int mQueriedNtnSignalStrengthLevel =
+            NTN_SIGNAL_STRENGTH_NONE;
+    private int mQueriedNtnSignalStrengthResultCode = SATELLITE_RESULT_SUCCESS;
+    private Semaphore mRequestNtnSignalStrengthSemaphore = new Semaphore(0);
+    private ResultReceiver mRequestNtnSignalStrengthReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedNtnSignalStrengthResultCode = resultCode;
+            logd("KEY_NTN_SIGNAL_STRENGTH: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                if (resultData.containsKey(KEY_NTN_SIGNAL_STRENGTH)) {
+                    NtnSignalStrength result = resultData.getParcelable(KEY_NTN_SIGNAL_STRENGTH);
+                    logd("result.getLevel()=" + result.getLevel());
+                    mQueriedNtnSignalStrengthLevel = result.getLevel();
+                } else {
+                    loge("KEY_NTN_SIGNAL_STRENGTH does not exist.");
+                    mQueriedNtnSignalStrengthLevel = NTN_SIGNAL_STRENGTH_NONE;
+                }
+            } else {
+                mQueriedNtnSignalStrengthLevel = NTN_SIGNAL_STRENGTH_NONE;
+            }
+            try {
+                mRequestNtnSignalStrengthSemaphore.release();
+            } catch (Exception ex) {
+                loge("mRequestNtnSignalStrengthReceiver: Got exception in releasing semaphore, ex="
+                        + ex);
+            }
+        }
+    };
 
     @Before
     public void setUp() throws Exception {
@@ -384,10 +458,20 @@
                 mMockSessionMetricsStats);
         replaceInstance(SubscriptionManagerService.class, "sInstance", null,
                 mMockSubscriptionManagerService);
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mPhone2});
+
+        mServiceState2 = Mockito.mock(ServiceState.class);
+        when(mPhone.getServiceState()).thenReturn(mServiceState);
+        when(mPhone.getSubId()).thenReturn(SUB_ID);
+        when(mPhone2.getServiceState()).thenReturn(mServiceState2);
+        when(mPhone2.getSubId()).thenReturn(SUB_ID1);
 
         mContextFixture.putStringArrayResource(
-                R.array.config_satellite_services_supported_by_providers,
-                EMPTY_SATELLITE_SERVICES_SUPPORTED_BY_PROVIDERS_STRING_ARRAY);
+                R.array.config_satellite_providers,
+                EMPTY_STRING_ARRAY);
+        mContextFixture.putIntResource(
+                R.integer.config_wait_for_satellite_enabling_response_timeout_millis,
+                TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
         doReturn(ACTIVE_SUB_IDS).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
 
         mCarrierConfigBundle = mContextFixture.getCarrierConfigBundle();
@@ -407,11 +491,13 @@
         doReturn(mIsSatelliteServiceSupported)
                 .when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         setUpResponseForRequestSatelliteCapabilities(
-                mSatelliteCapabilities, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
+                mSatelliteCapabilities, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteSupported(false,
+                SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         doNothing().when(mMockDatagramController).setDemoMode(anyBoolean());
         doNothing().when(mMockSatelliteSessionController)
                 .onSatelliteEnabledStateChanged(anyBoolean());
+        doNothing().when(mMockSatelliteSessionController).onSatelliteModemStateChanged(anyInt());
         doNothing().when(mMockSatelliteSessionController).setDemoMode(anyBoolean());
         doNothing().when(mMockControllerMetricsStats).onSatelliteEnabled();
         doNothing().when(mMockControllerMetricsStats).reportServiceEnablementSuccessCount();
@@ -425,10 +511,19 @@
         doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
                 .setResultCode(anyInt());
         doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
-                    .setIsProvisionRequest(eq(false));
+                .setIsProvisionRequest(eq(false));
         doNothing().when(mMockProvisionMetricsStats).reportProvisionMetrics();
         doNothing().when(mMockControllerMetricsStats).reportDeprovisionCount(anyInt());
-        mSatelliteControllerUT = new TestSatelliteController(mContext, Looper.myLooper());
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        doReturn(mSST).when(mPhone).getServiceStateTracker();
+        doReturn(mSST).when(mPhone2).getServiceStateTracker();
+        doReturn(mServiceState).when(mSST).getServiceState();
+        doReturn(Context.NOTIFICATION_SERVICE).when(mContext).getSystemServiceName(
+                NotificationManager.class);
+        doReturn(mMockNotificationManager).when(mContext).getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        mSatelliteControllerUT =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
         verify(mMockSatelliteModemInterface).registerForSatelliteProvisionStateChanged(
                 any(Handler.class),
                 eq(26) /* EVENT_SATELLITE_PROVISION_STATE_CHANGED */,
@@ -453,135 +548,181 @@
     @Test
     public void testRequestIsSatelliteCommunicationAllowedForCurrentLocation() {
         mSatelliteAllowedSemaphore.drainPermits();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
                 mSatelliteAllowedReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedSatelliteAllowedResultCode);
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, mQueriedSatelliteAllowedResultCode);
 
         resetSatelliteControllerUT();
         mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
                 mSatelliteAllowedReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteAllowedForCurrentLocation(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteAllowedForCurrentLocation(true,
+                SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
                 mSatelliteAllowedReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, mQueriedSatelliteAllowedResultCode);
+        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
         assertTrue(mQueriedSatelliteAllowed);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
                 mSatelliteAllowedReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(
-                SATELLITE_INVALID_MODEM_STATE);
+                SATELLITE_RESULT_INVALID_MODEM_STATE);
         mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
                 mSatelliteAllowedReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_INVALID_MODEM_STATE, mQueriedSatelliteAllowedResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, mQueriedSatelliteAllowedResultCode);
     }
 
     @Test
     public void testRequestTimeForNextSatelliteVisibility() {
         mSatelliteVisibilityTimeSemaphore.drainPermits();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
-        assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedSatelliteVisibilityTimeResultCode);
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, mQueriedSatelliteVisibilityTimeResultCode);
 
         resetSatelliteControllerUT();
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteVisibilityTimeResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                mQueriedSatelliteVisibilityTimeResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
-                SATELLITE_ERROR_NONE);
+                SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteVisibilityTimeResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                mQueriedSatelliteVisibilityTimeResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
-                SATELLITE_ERROR_NONE);
+                SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
-        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, mQueriedSatelliteVisibilityTimeResultCode);
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
+                mQueriedSatelliteVisibilityTimeResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        provisionSatelliteService();
         setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
-                SATELLITE_ERROR_NONE);
+                SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, mQueriedSatelliteVisibilityTimeResultCode);
+        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteVisibilityTimeResultCode);
         assertEquals(mSatelliteNextVisibilityTime, mQueriedSatelliteVisibilityTime);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        provisionSatelliteService();
         setUpNullResponseForRequestTimeForNextSatelliteVisibility(
-                SATELLITE_ERROR_NONE);
+                SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteVisibilityTimeResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                mQueriedSatelliteVisibilityTimeResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        provisionSatelliteService();
         setUpNullResponseForRequestTimeForNextSatelliteVisibility(
-                SATELLITE_INVALID_MODEM_STATE);
+                SATELLITE_RESULT_INVALID_MODEM_STATE);
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
-        assertEquals(SATELLITE_INVALID_MODEM_STATE, mQueriedSatelliteVisibilityTimeResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE,
+                mQueriedSatelliteVisibilityTimeResultCode);
+    }
+
+    @Test
+    public void testRadioStateChanged() {
+        mIsSatelliteEnabledSemaphore.drainPermits();
+
+        when(mMockSatelliteModemInterface.isSatelliteServiceConnected()).thenReturn(false);
+        setRadioPower(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, never())
+                .requestIsSatelliteSupported(any(Message.class));
+
+        setRadioPower(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, never())
+                .requestIsSatelliteSupported(any(Message.class));
+
+        when(mMockSatelliteModemInterface.isSatelliteServiceConnected()).thenReturn(true);
+        setRadioPower(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1))
+                .requestIsSatelliteSupported(any(Message.class));
+
+        setRadioPower(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(2))
+                .requestIsSatelliteSupported(any(Message.class));
+
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        setRadioPower(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(3))
+                .requestIsSatelliteSupported(any(Message.class));
+
+        setRadioPower(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(4))
+                .requestIsSatelliteSupported(any(Message.class));
+
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setRadioPower(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(5))
+                .requestIsSatelliteSupported(any(Message.class));
+
+        setRadioPower(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(5))
+                .requestIsSatelliteSupported(any(Message.class));
     }
 
     @Test
@@ -593,16 +734,17 @@
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
 
         // Fail to enable satellite when the device does not support satellite.
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
 
         // Fail to enable satellite when the device is not provisioned yet.
         mIIntegerConsumerResults.clear();
@@ -610,46 +752,47 @@
         verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(false));
         verify(mMockSatelliteSessionController, times(1)).setDemoMode(eq(false));
         verify(mMockDatagramController, times(1)).setDemoMode(eq(false));
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
+                (long) mIIntegerConsumerResults.get(0));
 
         sendProvisionedStateChangedEvent(true, null);
         processAllMessages();
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
 
         // Successfully enable satellite
         mIIntegerConsumerResults.clear();
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
         assertEquals(
                 SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue);
         verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(true));
         verify(mMockSatelliteSessionController, times(2)).setDemoMode(eq(false));
         verify(mMockDatagramController, times(2)).setDemoMode(eq(false));
-        verify(mMockPointingAppController).startPointingUI(eq(false));
         verify(mMockControllerMetricsStats, times(1)).onSatelliteEnabled();
         verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementSuccessCount();
 
         // Successfully disable satellite when radio is turned off.
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
         setRadioPower(false);
+        mSatelliteControllerUT.onCellularRadioPowerOffRequested();
         processAllMessages();
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
         processAllMessages();
-        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
         assertEquals(
                 SATELLITE_MODE_ENABLED_FALSE, mSatelliteControllerUT.satelliteModeSettingValue);
@@ -660,27 +803,27 @@
 
         // Fail to enable satellite when radio is off.
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         // Radio is not on, can not enable satellite
-        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
 
         setRadioPower(true);
         processAllMessages();
-        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
 
         // Fail to enable satellite with an error response from modem when radio is on.
         mIIntegerConsumerResults.clear();
         clearInvocations(mMockPointingAppController);
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_INVALID_MODEM_STATE);
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_INVALID_MODEM_STATE);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
         verify(mMockPointingAppController, never()).startPointingUI(anyBoolean());
         assertFalse(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
         verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementFailCount();
@@ -688,64 +831,63 @@
         // Successfully enable satellite when radio is on.
         mIIntegerConsumerResults.clear();
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
         assertEquals(SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue);
-        verify(mMockPointingAppController).startPointingUI(eq(false));
         verify(mMockSatelliteSessionController, times(2)).onSatelliteEnabledStateChanged(eq(true));
         verify(mMockSatelliteSessionController, times(4)).setDemoMode(eq(false));
         verify(mMockDatagramController, times(4)).setDemoMode(eq(false));
         verify(mMockControllerMetricsStats, times(2)).onSatelliteEnabled();
         verify(mMockControllerMetricsStats, times(2)).reportServiceEnablementSuccessCount();
-        verify(mMockSessionMetricsStats, times(3)).setInitializationResult(anyInt());
-        verify(mMockSessionMetricsStats, times(3)).setRadioTechnology(anyInt());
-        verify(mMockSessionMetricsStats, times(3)).reportSessionMetrics();
+        verify(mMockSessionMetricsStats, times(7)).setInitializationResult(anyInt());
+        verify(mMockSessionMetricsStats, times(7)).setRadioTechnology(anyInt());
+        verify(mMockSessionMetricsStats, times(7)).reportSessionMetrics();
 
         // Successfully enable satellite when it is already enabled.
         mIIntegerConsumerResults.clear();
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
 
         // Fail to enable satellite with a different demo mode when it is already enabled.
         mIIntegerConsumerResults.clear();
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, true, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_ARGUMENTS, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+        assertEquals(SATELLITE_RESULT_INVALID_ARGUMENTS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
 
         // Successfully disable satellite.
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
 
         // Disable satellite when satellite is already disabled.
         mIIntegerConsumerResults.clear();
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
 
         // Disable satellite with a different demo mode when satellite is already disabled.
         mIIntegerConsumerResults.clear();
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, true, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
 
         // Send a second request while the first request in progress
         mIIntegerConsumerResults.clear();
@@ -756,13 +898,13 @@
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_REQUEST_IN_PROGRESS, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_REQUEST_IN_PROGRESS, (long) mIIntegerConsumerResults.get(0));
 
         mIIntegerConsumerResults.clear();
         resetSatelliteControllerUTToSupportedAndProvisionedState();
         // Should receive callback for the above request when satellite modem is turned off.
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
 
         // Move to satellite-disabling in progress.
         setUpNoResponseForRequestSatelliteEnabled(false, false);
@@ -775,53 +917,62 @@
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_ERROR, (long) mIIntegerConsumerResults.get(0));
 
         mIIntegerConsumerResults.clear();
         resetSatelliteControllerUTToOffAndProvisionedState();
         // Should receive callback for the above request when satellite modem is turned off.
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
 
         /**
          * Make areAllRadiosDisabled return false and move mWaitingForRadioDisabled to true, which
          * will lead to no response for requestSatelliteEnabled.
          */
         mSatelliteControllerUT.allRadiosDisabled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
 
         resetSatelliteControllerUTEnabledState();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
         processAllMessages();
         // We should receive 2 callbacks for the above 2 requests.
         assertTrue(waitForIIntegerConsumerResult(2));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(1));
 
         resetSatelliteControllerUTToOffAndProvisionedState();
 
         // Repeat the same test as above but with error response from modem for the second request
         mSatelliteControllerUT.allRadiosDisabled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
 
         resetSatelliteControllerUTEnabledState();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_NO_RESOURCES);
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_NO_RESOURCES);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
         processAllMessages();
         // We should receive 2 callbacks for the above 2 requests.
         assertTrue(waitForIIntegerConsumerResult(2));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        assertEquals(SATELLITE_NO_RESOURCES, (long) mIIntegerConsumerResults.get(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_NO_RESOURCES, (long) mIIntegerConsumerResults.get(1));
         mSatelliteControllerUT.allRadiosDisabled = true;
+
+        resetSatelliteControllerUTToOnAndProvisionedState();
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+        mSatelliteControllerUT.onCellularRadioPowerOffRequested();
+        processAllMessages();
+        // Satellite should not be powered off since the feature flag oemEnabledSatelliteFlag is
+        // disabled
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
     }
 
     @Test
@@ -830,42 +981,48 @@
         mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteCapabilitiesResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                mQueriedSatelliteCapabilitiesResultCode);
 
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
-        assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedSatelliteCapabilitiesResultCode);
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, mQueriedSatelliteCapabilitiesResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestSatelliteCapabilities(mSatelliteCapabilities, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestSatelliteCapabilities(mSatelliteCapabilities,
+                SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, mQueriedSatelliteCapabilitiesResultCode);
+        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteCapabilitiesResultCode);
         assertEquals(mSatelliteCapabilities, mQueriedSatelliteCapabilities);
+        assertTrue(
+                mQueriedSatelliteCapabilities.getSupportedRadioTechnologies().contains(
+                        mSatelliteControllerUT.getSupportedNtnRadioTechnology()));
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteCapabilitiesResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                mQueriedSatelliteCapabilitiesResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_INVALID_MODEM_STATE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_RESULT_INVALID_MODEM_STATE);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
-        assertEquals(SATELLITE_INVALID_MODEM_STATE, mQueriedSatelliteCapabilitiesResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, mQueriedSatelliteCapabilitiesResultCode);
     }
 
     @Test
@@ -876,72 +1033,69 @@
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
 
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
+                (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
+        provisionSatelliteService();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE);
+        setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         verify(mMockPointingAppController).registerForSatelliteTransmissionUpdates(anyInt(),
-                eq(mStartTransmissionUpdateCallback), any());
+                eq(mStartTransmissionUpdateCallback));
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        verify(mMockPointingAppController).startSatelliteTransmissionUpdates(any(Message.class),
-                any(Phone.class));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockPointingAppController).startSatelliteTransmissionUpdates(any(Message.class));
         verify(mMockPointingAppController).setStartedSatelliteTransmissionUpdates(eq(true));
 
         resetSatelliteControllerUT();
+        provisionSatelliteService();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_INVALID_TELEPHONY_STATE);
+        setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
         mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
         verify(mMockPointingAppController).unregisterForSatelliteTransmissionUpdates(anyInt(),
-                any(),  eq(mStartTransmissionUpdateCallback), any(Phone.class));
+                any(), eq(mStartTransmissionUpdateCallback));
         verify(mMockPointingAppController).setStartedSatelliteTransmissionUpdates(eq(false));
     }
 
@@ -953,69 +1107,66 @@
                 mStopTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
 
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStopTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStopTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStopTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
+                (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE);
+        provisionSatelliteService();
+        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStopTransmissionUpdateCallback);
         verify(mMockPointingAppController).unregisterForSatelliteTransmissionUpdates(anyInt(),
-                any(),  eq(mStopTransmissionUpdateCallback), any(Phone.class));
+                any(), eq(mStopTransmissionUpdateCallback));
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        verify(mMockPointingAppController).stopSatelliteTransmissionUpdates(any(Message.class),
-                any(Phone.class));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockPointingAppController).stopSatelliteTransmissionUpdates(any(Message.class));
 
         resetSatelliteControllerUT();
+        provisionSatelliteService();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_INVALID_TELEPHONY_STATE);
+        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
         mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStopTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
     }
 
     @Test
@@ -1024,79 +1175,86 @@
         resetSatelliteControllerUT();
         mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode);
         assertFalse(mQueriedIsDemoModeEnabled);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
-        assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedIsDemoModeEnabledResultCode);
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, mQueriedIsDemoModeEnabledResultCode);
         assertFalse(mQueriedIsDemoModeEnabled);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode);
         assertFalse(mQueriedIsDemoModeEnabled);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
-        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, mQueriedIsDemoModeEnabledResultCode);
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED, mQueriedIsDemoModeEnabledResultCode);
         assertFalse(mQueriedIsDemoModeEnabled);
 
         resetSatelliteControllerUT();
         boolean isDemoModeEnabled = mSatelliteControllerUT.isDemoModeEnabled();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        provisionSatelliteService();
         mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, mQueriedIsDemoModeEnabledResultCode);
+        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedIsDemoModeEnabledResultCode);
         assertEquals(isDemoModeEnabled, mQueriedIsDemoModeEnabled);
     }
 
     @Test
     public void testIsSatelliteEnabled() {
+        setUpResponseForRequestIsSatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
         assertFalse(mSatelliteControllerUT.isSatelliteEnabled());
-        setUpResponseForRequestIsSatelliteEnabled(true, SATELLITE_ERROR_NONE);
         mIsSatelliteEnabledSemaphore.drainPermits();
         mSatelliteControllerUT.requestIsSatelliteEnabled(SUB_ID, mIsSatelliteEnabledReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteEnabledResult(1));
+        assertEquals(
+                SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedIsSatelliteEnabledResultCode);
+
+
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestIsSatelliteEnabled(SUB_ID, mIsSatelliteEnabledReceiver);
+        processAllMessages();
+        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedIsSatelliteEnabledResultCode);
         assertEquals(mSatelliteControllerUT.isSatelliteEnabled(), mQueriedIsSatelliteEnabled);
     }
 
     @Test
     public void testOnSatelliteServiceConnected() {
-        verifySatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
-        verifySatelliteEnabled(false, SATELLITE_INVALID_TELEPHONY_STATE);
-        verifySatelliteProvisioned(false, SATELLITE_INVALID_TELEPHONY_STATE);
+        verifySatelliteSupported(false, SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        verifySatelliteEnabled(false, SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
 
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
 
+        setUpResponseForRequestIsSatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.onSatelliteServiceConnected();
         processAllMessages();
 
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
     }
 
     @Test
     public void testRegisterForSatelliteModemStateChanged() {
-        ISatelliteStateCallback callback = new ISatelliteStateCallback.Stub() {
+        ISatelliteModemStateCallback callback = new ISatelliteModemStateCallback.Stub() {
             @Override
             public void onSatelliteModemStateChanged(int state) {
                 logd("onSatelliteModemStateChanged: state=" + state);
@@ -1104,7 +1262,7 @@
         };
         int errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(
                 SUB_ID, callback);
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, errorCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
         verify(mMockSatelliteSessionController, never())
                 .registerForSatelliteModemStateChanged(callback);
 
@@ -1112,25 +1270,25 @@
 
         errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(
                 SUB_ID, callback);
-        assertEquals(SATELLITE_ERROR_NONE, errorCode);
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
         verify(mMockSatelliteSessionController).registerForSatelliteModemStateChanged(callback);
     }
 
     @Test
     public void testUnregisterForSatelliteModemStateChanged() {
-        ISatelliteStateCallback callback = new ISatelliteStateCallback.Stub() {
+        ISatelliteModemStateCallback callback = new ISatelliteModemStateCallback.Stub() {
             @Override
             public void onSatelliteModemStateChanged(int state) {
                 logd("onSatelliteModemStateChanged: state=" + state);
             }
         };
-        mSatelliteControllerUT.unregisterForSatelliteModemStateChanged(SUB_ID, callback);
+        mSatelliteControllerUT.unregisterForModemStateChanged(SUB_ID, callback);
         verify(mMockSatelliteSessionController, never())
                 .unregisterForSatelliteModemStateChanged(callback);
 
         resetSatelliteControllerUTToSupportedAndProvisionedState();
 
-        mSatelliteControllerUT.unregisterForSatelliteModemStateChanged(SUB_ID, callback);
+        mSatelliteControllerUT.unregisterForModemStateChanged(SUB_ID, callback);
         verify(mMockSatelliteSessionController).unregisterForSatelliteModemStateChanged(callback);
     }
 
@@ -1152,20 +1310,20 @@
                 };
         int errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(
                 SUB_ID, callback);
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, errorCode);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
 
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(
                 SUB_ID, callback);
-        assertEquals(SATELLITE_NOT_SUPPORTED, errorCode);
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, errorCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(
                 SUB_ID, callback);
-        assertEquals(SATELLITE_ERROR_NONE, errorCode);
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
 
         sendProvisionedStateChangedEvent(true, null);
         processAllMessages();
@@ -1191,9 +1349,9 @@
                     }
                 };
         when(mMockDatagramController.registerForSatelliteDatagram(eq(SUB_ID), eq(callback)))
-                .thenReturn(SATELLITE_ERROR_NONE);
-        int errorCode = mSatelliteControllerUT.registerForSatelliteDatagram(SUB_ID, callback);
-        assertEquals(SATELLITE_ERROR_NONE, errorCode);
+                .thenReturn(SATELLITE_RESULT_SUCCESS);
+        int errorCode = mSatelliteControllerUT.registerForIncomingDatagram(SUB_ID, callback);
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
         verify(mMockDatagramController).registerForSatelliteDatagram(eq(SUB_ID), eq(callback));
     }
 
@@ -1210,7 +1368,7 @@
                 };
         doNothing().when(mMockDatagramController)
                 .unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback));
-        mSatelliteControllerUT.unregisterForSatelliteDatagram(SUB_ID, callback);
+        mSatelliteControllerUT.unregisterForIncomingDatagram(SUB_ID, callback);
         verify(mMockDatagramController).unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback));
     }
 
@@ -1220,26 +1378,28 @@
         SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
 
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID,
+        mSatelliteControllerUT.sendDatagram(SUB_ID,
                 SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
         verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(),
                 eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true),
                 any());
 
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         sendProvisionedStateChangedEvent(false, null);
         processAllMessages();
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID,
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.sendDatagram(SUB_ID,
                 SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
+                (long) mIIntegerConsumerResults.get(0));
         verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(),
                 eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true),
                 any());
@@ -1247,8 +1407,8 @@
         mIIntegerConsumerResults.clear();
         sendProvisionedStateChangedEvent(true, null);
         processAllMessages();
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID,
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.sendDatagram(SUB_ID,
                 SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
@@ -1261,29 +1421,31 @@
     @Test
     public void testPollPendingSatelliteDatagrams() {
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer);
+        mSatelliteControllerUT.pollPendingDatagrams(SUB_ID, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
         verify(mMockDatagramController, never()).pollPendingSatelliteDatagrams(anyInt(), any());
 
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         sendProvisionedStateChangedEvent(false, null);
         processAllMessages();
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.pollPendingDatagrams(SUB_ID, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
+                (long) mIIntegerConsumerResults.get(0));
         verify(mMockDatagramController, never()).pollPendingSatelliteDatagrams(anyInt(), any());
 
         mIIntegerConsumerResults.clear();
         sendProvisionedStateChangedEvent(true, null);
         processAllMessages();
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer);
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.pollPendingDatagrams(SUB_ID, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
         verify(mMockDatagramController, times(1)).pollPendingSatelliteDatagrams(anyInt(), any());
@@ -1301,94 +1463,122 @@
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
         assertNull(cancelRemote);
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
         assertNull(cancelRemote);
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
-                TEST_SATELLITE_TOKEN,
-                testProvisionData, mIIntegerConsumer);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        assertNull(cancelRemote);
-
-        resetSatelliteControllerUT();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_ERROR_NONE);
+                SATELLITE_RESULT_SUCCESS);
         cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        assertNotNull(cancelRemote);
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+
+        // Send provision request again after the device is successfully provisioned
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        assertNull(cancelRemote);
+
+        // Vendor service does not support the request requestIsSatelliteProvisioned. Telephony will
+        // make decision itself
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(
+                false, SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+
+        // Vendor service does not support the requests requestIsSatelliteProvisioned and
+        // provisionSatelliteService. Telephony will make decision itself
+        deprovisionSatelliteService();
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(
+                false, SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        assertNotNull(cancelRemote);
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
+                SATELLITE_RESULT_NOT_AUTHORIZED);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_NOT_AUTHORIZED, (long) mIIntegerConsumerResults.get(0));
         assertNotNull(cancelRemote);
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_NOT_AUTHORIZED);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
-                TEST_SATELLITE_TOKEN,
-                testProvisionData, mIIntegerConsumer);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_NOT_AUTHORIZED, (long) mIIntegerConsumerResults.get(0));
-        assertNotNull(cancelRemote);
-
-        resetSatelliteControllerUT();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         setUpResponseForProvisionSatelliteService(TEST_NEXT_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_ERROR_NONE);
+                SATELLITE_RESULT_SUCCESS);
         cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
                 TEST_NEXT_SATELLITE_TOKEN, testProvisionData, mIIntegerConsumer);
         cancellationSignal.setRemote(cancelRemote);
         cancellationSignal.cancel();
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
         verify(mMockSatelliteModemInterface).deprovisionSatelliteService(
                 eq(TEST_NEXT_SATELLITE_TOKEN), any(Message.class));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         setUpNoResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN);
         setUpResponseForProvisionSatelliteService(TEST_NEXT_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_ERROR_NONE);
+                SATELLITE_RESULT_SUCCESS);
         cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
@@ -1397,7 +1587,7 @@
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_SERVICE_PROVISION_IN_PROGRESS,
+        assertEquals(SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS,
                 (long) mIIntegerConsumerResults.get(0));
     }
 
@@ -1405,123 +1595,119 @@
     public void testDeprovisionSatelliteService() {
         mIIntegerConsumerSemaphore.drainPermits();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
         mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
-                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
-
-        resetSatelliteControllerUT();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_ERROR_NONE);
-        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
-        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
+        provisionSatelliteService();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+
+        // Vendor service does not support deprovisionSatelliteService
+        resetSatelliteControllerUT();
+        provisionSatelliteService();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForDeprovisionSatelliteService(
+                TEST_SATELLITE_TOKEN, SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+
+        resetSatelliteControllerUT();
+        provisionSatelliteService();
+        mIIntegerConsumerResults.clear();
         setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN,
-                SATELLITE_INVALID_MODEM_STATE);
+                SATELLITE_RESULT_INVALID_MODEM_STATE);
         mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
     }
 
     @Test
     public void testSupportedSatelliteServices() {
-        List<String> satellitePlmnList = mSatelliteControllerUT.getSatellitePlmnList();
-        assertEquals(EMPTY_SATELLITE_SERVICES_SUPPORTED_BY_PROVIDERS_STRING_ARRAY.length,
-                satellitePlmnList.size());
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
+        List<String> satellitePlmnList = mSatelliteControllerUT.getSatellitePlmnsForCarrier(
+                SUB_ID);
+        assertEquals(EMPTY_STRING_ARRAY.length, satellitePlmnList.size());
         List<Integer> supportedSatelliteServices =
                 mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101");
         assertTrue(supportedSatelliteServices.isEmpty());
 
-        String[] satellitePlmnArray = {"00101", "00102"};
-        String[] satelliteServicesSupportedByProviderStrArray = {"00101:1,2", "00102:2,3"};
-        int[] expectedSupportedServices1 = {1, 2};
-        int[] expectedSupportedServices2 = {2, 3};
-
+        String[] satelliteProviderStrArray = {"00101", "00102"};
         mContextFixture.putStringArrayResource(
-                R.array.config_satellite_services_supported_by_providers,
-                satelliteServicesSupportedByProviderStrArray);
-        TestSatelliteController testSatelliteController =
-                new TestSatelliteController(mContext, Looper.myLooper());
-
-        satellitePlmnList = testSatelliteController.getSatellitePlmnList();
-        assertTrue(Arrays.equals(satellitePlmnArray, satellitePlmnList.stream().toArray()));
-
-        supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
-        assertNotNull(supportedSatelliteServices);
-        assertTrue(Arrays.equals(expectedSupportedServices1,
-                supportedSatelliteServices.stream()
-                        .mapToInt(Integer::intValue)
-                        .toArray()));
-
-        supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
-        assertNotNull(supportedSatelliteServices);
-        assertTrue(Arrays.equals(expectedSupportedServices2,
-                supportedSatelliteServices.stream()
-                        .mapToInt(Integer::intValue)
-                        .toArray()));
-
-        // Carrier config changed
-        int[] expectedSupportedServices3 = {2};
-        int[] supportedServices = {1, 3};
+                R.array.config_satellite_providers, satelliteProviderStrArray);
+        int[] expectedSupportedServices2 = {2};
+        int[] expectedSupportedServices3 = {1, 3};
         PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle();
         carrierSupportedSatelliteServicesPerProvider.putIntArray(
-                "00102", expectedSupportedServices3);
-        carrierSupportedSatelliteServicesPerProvider.putIntArray("00103", supportedServices);
+                "00102", expectedSupportedServices2);
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                "00103", expectedSupportedServices3);
+        String[] expectedSupportedSatellitePlmns = {"00102", "00103"};
         mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
                         .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
                 carrierSupportedSatelliteServicesPerProvider);
+        TestSatelliteController testSatelliteController =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+
+        satellitePlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
+        assertTrue(satellitePlmnList.isEmpty());
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
+        assertTrue(supportedSatelliteServices.isEmpty());
+
+        // Carrier config changed with carrierEnabledSatelliteFlag disabled
         for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
                 : mCarrierConfigChangedListenerList) {
             pair.first.execute(() -> pair.second.onCarrierConfigChanged(
@@ -1531,25 +1717,38 @@
         processAllMessages();
 
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
-        assertNotNull(supportedSatelliteServices);
-        assertTrue(Arrays.equals(expectedSupportedServices1,
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
+        assertTrue(supportedSatelliteServices.isEmpty());
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00103");
+        assertTrue(supportedSatelliteServices.isEmpty());
+
+        // Trigger carrier config changed with carrierEnabledSatelliteFlag enabled
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        satellitePlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
+        assertTrue(Arrays.equals(
+                expectedSupportedSatellitePlmns, satellitePlmnList.stream().toArray()));
+        supportedSatelliteServices =
+                mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00102");
+        assertTrue(Arrays.equals(expectedSupportedServices2,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
-
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
-        assertNotNull(supportedSatelliteServices);
+                mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00103");
         assertTrue(Arrays.equals(expectedSupportedServices3,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
 
-        supportedSatelliteServices =
-                mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00103");
-        assertTrue(supportedSatelliteServices.isEmpty());
-
         // Subscriptions changed
         int[] newActiveSubIds = {SUB_ID1};
         doReturn(newActiveSubIds).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
@@ -1561,47 +1760,1382 @@
         }
         processAllMessages();
 
-        supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
-        assertTrue(supportedSatelliteServices.isEmpty());
+        satellitePlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
+        assertTrue(satellitePlmnList.isEmpty());
         supportedSatelliteServices =
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
         assertTrue(supportedSatelliteServices.isEmpty());
-
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00101");
-        assertNotNull(supportedSatelliteServices);
-        assertTrue(Arrays.equals(expectedSupportedServices1,
-                supportedSatelliteServices.stream()
-                        .mapToInt(Integer::intValue)
-                        .toArray()));
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00103");
+        assertTrue(supportedSatelliteServices.isEmpty());
+
 
         supportedSatelliteServices =
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00102");
         assertNotNull(supportedSatelliteServices);
-        assertTrue(Arrays.equals(expectedSupportedServices3,
+        assertTrue(Arrays.equals(expectedSupportedServices2,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
 
         supportedSatelliteServices =
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00103");
-        assertTrue(supportedSatelliteServices.isEmpty());
+        assertTrue(Arrays.equals(expectedSupportedServices3,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+    }
+
+    @Test
+    public void testConfigureSatellitePlmnOnCarrierConfigChanged() {
+        logd("testConfigureSatellitePlmnOnCarrierConfigChanged");
+
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
+        String[] satelliteProviderStrArray =
+                {"00101", "00102", "00103", "00104", "00105"};
+        List<String> satellitePlmnListFromOverlayConfig =
+                Arrays.stream(satelliteProviderStrArray).toList();
+        mContextFixture.putStringArrayResource(
+                R.array.config_satellite_providers, satelliteProviderStrArray);
+
+        /* Initially, the radio state is ON. In the constructor, satelliteController registers for
+         the radio state changed events and immediately gets the radio state changed event as ON. */
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mCarrierConfigChangedListenerList.clear();
+        TestSatelliteController testSatelliteController =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        processAllMessages();
+        List<String> carrierPlmnList = testSatelliteController.getSatellitePlmnsForCarrier(
+                SUB_ID);
+        verify(mMockSatelliteModemInterface, never()).setSatellitePlmn(
+                anyInt(), anyList(), anyList(), any(Message.class));
+        assertTrue(carrierPlmnList.isEmpty());
+        reset(mMockSatelliteModemInterface);
+
+        // Test setSatellitePlmn() when Carrier Config change event triggered.
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+        int[] supportedServices2 = {2};
+        int[] supportedServices3 = {1, 3};
+        PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle();
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                "00102", supportedServices2);
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                "00103", supportedServices3);
+        List<String> expectedCarrierPlmnList = Arrays.asList("00102", "00103");
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                carrierSupportedSatelliteServicesPerProvider);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        carrierPlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
+        verify(mMockSatelliteModemInterface, never()).setSatellitePlmn(
+                anyInt(), anyList(), anyList(), any(Message.class));
+        assertTrue(carrierPlmnList.isEmpty());
+        reset(mMockSatelliteModemInterface);
+
+        // Reset TestSatelliteController so that device satellite PLMNs is loaded when
+        // carrierEnabledSatelliteFlag is enabled.
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mCarrierConfigChangedListenerList.clear();
+        testSatelliteController =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+
+        // Trigger carrier config changed with carrierEnabledSatelliteFlag enabled and empty
+        // carrier supported satellite services.
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                new PersistableBundle());
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        carrierPlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
+        assertTrue(carrierPlmnList.isEmpty());
+        List<String> allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
+                carrierPlmnList, satellitePlmnListFromOverlayConfig);
+        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
+                eq(EMPTY_STRING_LIST), eq(allSatellitePlmnList), any(Message.class));
+        reset(mMockSatelliteModemInterface);
+
+        // Trigger carrier config changed with carrierEnabledSatelliteFlag enabled and non-empty
+        // carrier supported satellite services.
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                carrierSupportedSatelliteServicesPerProvider);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        carrierPlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
+        allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
+                carrierPlmnList, satellitePlmnListFromOverlayConfig);
+        assertEquals(expectedCarrierPlmnList, carrierPlmnList);
+        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
+                eq(carrierPlmnList), eq(allSatellitePlmnList), any(Message.class));
+        reset(mMockSatelliteModemInterface);
+
+        /* setSatellitePlmn() is called regardless whether satellite attach for carrier is
+           supported. */
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                false);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
+                eq(carrierPlmnList), eq(allSatellitePlmnList), any(Message.class));
+        reset(mMockSatelliteModemInterface);
+
+        // Test empty config_satellite_providers and empty carrier PLMN list
+        mCarrierConfigChangedListenerList.clear();
+        mContextFixture.putStringArrayResource(
+                R.array.config_satellite_providers, EMPTY_STRING_ARRAY);
+        testSatelliteController =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                new PersistableBundle());
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        carrierPlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
+        assertTrue(carrierPlmnList.isEmpty());
+        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
+                eq(EMPTY_STRING_LIST), eq(EMPTY_STRING_LIST), any(Message.class));
+        reset(mMockSatelliteModemInterface);
+    }
+
+    @Test
+    public void testSatelliteCommunicationRestriction() {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        // Remove restriction reason if exist
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(2));
+
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(1));
+
+        Set<Integer> restrictionSet =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertTrue(!restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
+        assertTrue(!restrictionSet.contains(
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION));
+
+        // Add satellite attach restriction reason by user
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mSatelliteControllerUT.addAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
+        processAllMessages();
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockSatelliteModemInterface, never())
+                .requestSetSatelliteEnabledForCarrier(anyInt(), anyBoolean(), any(Message.class));
+        assertTrue(waitForIIntegerConsumerResult(1));
+        restrictionSet =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertTrue(restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
+
+        // remove satellite restriction reason by user
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(true, SATELLITE_RESULT_SUCCESS);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        restrictionSet =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertTrue(!restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
+        verify(mMockSatelliteModemInterface, times(1))
+                .requestSetSatelliteEnabledForCarrier(anyInt(), anyBoolean(), any(Message.class));
+
+        // Add satellite attach restriction reason by user
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mSatelliteControllerUT.addAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        restrictionSet =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertTrue(restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
+        verify(mMockSatelliteModemInterface, times(1))
+                .requestSetSatelliteEnabledForCarrier(anyInt(), eq(false), any(Message.class));
+
+        // add satellite attach restriction reason by geolocation
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.addAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION, mIIntegerConsumer);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        restrictionSet =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertTrue(restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION));
+        verify(mMockSatelliteModemInterface, never())
+                .requestSetSatelliteEnabledForCarrier(anyInt(), anyBoolean(), any(Message.class));
+
+        // remove satellite attach restriction reason by geolocation
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION, mIIntegerConsumer);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        restrictionSet =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertTrue(!restrictionSet.contains(
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION));
+        verify(mMockSatelliteModemInterface, never())
+                .requestSetSatelliteEnabledForCarrier(anyInt(), anyBoolean(), any(Message.class));
+
+        // remove satellite restriction reason by user
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        restrictionSet =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertTrue(!restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
+        verify(mMockSatelliteModemInterface, times(1))
+                .requestSetSatelliteEnabledForCarrier(anyInt(), eq(true), any(Message.class));
+        reset(mMockSatelliteModemInterface);
+
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
+
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.addAttachRestrictionForCarrier(SUB_ID,
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+
+        Set<Integer> satelliteRestrictionReasons =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertTrue(satelliteRestrictionReasons.isEmpty());
+    }
+
+    @Test
+    public void testIsSatelliteAttachRequired() {
+        TestSatelliteController satelliteController =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        mSatelliteCapabilitiesSemaphore.drainPermits();
+        satelliteController.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
+        assertEquals(
+                SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedSatelliteCapabilitiesResultCode);
+        assertFalse(satelliteController.isSatelliteAttachRequired());
+
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestSatelliteCapabilities(
+                mEmptySatelliteCapabilities, SATELLITE_RESULT_SUCCESS);
+        satelliteController =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        verifySatelliteSupported(satelliteController, true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteCapabilitiesSemaphore.drainPermits();
+        satelliteController.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteCapabilitiesResultCode);
+        assertEquals(mEmptySatelliteCapabilities, mQueriedSatelliteCapabilities);
+        assertEquals(SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN,
+                mSatelliteControllerUT.getSupportedNtnRadioTechnology());
+
+        assertFalse(satelliteController.isSatelliteAttachRequired());
+
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestSatelliteCapabilities(
+                mSatelliteCapabilities, SATELLITE_RESULT_SUCCESS);
+        satelliteController =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        verifySatelliteSupported(satelliteController, true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteCapabilitiesSemaphore.drainPermits();
+        satelliteController.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteCapabilitiesResultCode);
+        assertEquals(mSatelliteCapabilities, mQueriedSatelliteCapabilities);
+        assertTrue(
+                mQueriedSatelliteCapabilities.getSupportedRadioTechnologies().contains(
+                        satelliteController.getSupportedNtnRadioTechnology()));
+        assertTrue(satelliteController.isSatelliteAttachRequired());
+
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+        assertFalse(satelliteController.isSatelliteAttachRequired());
+    }
+
+    @Test
+    public void testSatelliteModemStateChanged() {
+        clearInvocations(mMockSatelliteSessionController);
+        clearInvocations(mMockDatagramController);
+        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_CONNECTED, null);
+        processAllMessages();
+        verify(mMockSatelliteSessionController, times(0)).onSatelliteModemStateChanged(
+                SATELLITE_MODEM_STATE_CONNECTED);
+
+        resetSatelliteControllerUTToSupportedAndProvisionedState();
+        clearInvocations(mMockSatelliteSessionController);
+        clearInvocations(mMockDatagramController);
+        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_UNAVAILABLE, null);
+        processAllMessages();
+        verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(false));
+        verify(mMockSatelliteSessionController, times(1)).setDemoMode(eq(false));
+        verify(mMockDatagramController, times(1)).setDemoMode(eq(false));
+
+        clearInvocations(mMockSatelliteSessionController);
+        clearInvocations(mMockDatagramController);
+        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_CONNECTED, null);
+        processAllMessages();
+        verify(mMockSatelliteSessionController, times(1)).onSatelliteModemStateChanged(
+                SATELLITE_MODEM_STATE_CONNECTED);
+    }
+
+    @Test
+    public void testRequestNtnSignalStrengthWithFeatureFlagEnabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        resetSatelliteControllerUT();
+
+        mRequestNtnSignalStrengthSemaphore.drainPermits();
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+
+        @NtnSignalStrength.NtnSignalStrengthLevel int expectedLevel = NTN_SIGNAL_STRENGTH_GREAT;
+        setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+        /* In case request is not successful, result should be NTN_SIGNAL_STRENGTH_NONE */
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE, SATELLITE_RESULT_NOT_SUPPORTED);
+
+        resetSatelliteControllerUT();
+        provisionSatelliteService();
+
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+        verifyRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+
+        resetSatelliteControllerUT();
+        provisionSatelliteService();
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        // reset cache to NTN_SIGNAL_STRENGTH_NONE
+        sendNtnSignalStrengthChangedEvent(NTN_SIGNAL_STRENGTH_NONE, null);
+        processAllMessages();
+        expectedLevel = NTN_SIGNAL_STRENGTH_POOR;
+        setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+        verifyRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+    }
+
+    @Test
+    public void testRequestNtnSignalStrengthWithFeatureFlagDisabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+
+        resetSatelliteControllerUT();
+        mRequestNtnSignalStrengthSemaphore.drainPermits();
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+
+        @NtnSignalStrength.NtnSignalStrengthLevel int expectedLevel = NTN_SIGNAL_STRENGTH_GREAT;
+        setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+
+        expectedLevel = NTN_SIGNAL_STRENGTH_POOR;
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_MODEM_ERROR);
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+    }
+
+    @Test
+    public void testRegisterForNtnSignalStrengthChangedWithFeatureFlagEnabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        Semaphore semaphore = new Semaphore(0);
+        final NtnSignalStrength[] signalStrength = new NtnSignalStrength[1];
+        INtnSignalStrengthCallback callback =
+                new INtnSignalStrengthCallback.Stub() {
+                    @Override
+                    public void onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength) {
+                        logd("onNtnSignalStrengthChanged: ntnSignalStrength="
+                                + ntnSignalStrength);
+                        try {
+                            signalStrength[0] = ntnSignalStrength;
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onNtnSignalStrengthChanged: Got exception in releasing "
+                                    + "semaphore, ex=" + ex);
+                        }
+                    }
+                };
+
+        verifyRegisterForNtnSignalStrengthChanged(SUB_ID, callback,
+                SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
+
+        setUpResponseForRequestIsSatelliteSupported(false,
+                SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifyRegisterForNtnSignalStrengthChanged(SUB_ID, callback,
+                SATELLITE_RESULT_NOT_SUPPORTED);
+
+        @NtnSignalStrength.NtnSignalStrengthLevel int expectedLevel = NTN_SIGNAL_STRENGTH_NONE;
+        verifyRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_NOT_SUPPORTED);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+        provisionSatelliteService();
+        verifyRegisterForNtnSignalStrengthChanged(SUB_ID, callback,
+                SATELLITE_RESULT_SUCCESS);
+        verifyRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
+
+        expectedLevel = NTN_SIGNAL_STRENGTH_GOOD;
+        sendNtnSignalStrengthChangedEvent(expectedLevel, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForNtnSignalStrengthChanged"));
+        assertEquals(expectedLevel, signalStrength[0].getLevel());
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_GOOD, SATELLITE_RESULT_SUCCESS);
+
+        expectedLevel = NTN_SIGNAL_STRENGTH_POOR;
+        sendNtnSignalStrengthChangedEvent(expectedLevel, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForNtnSignalStrengthChanged"));
+        assertEquals(expectedLevel, signalStrength[0].getLevel());
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_POOR, SATELLITE_RESULT_SUCCESS);
+
+        mSatelliteControllerUT.unregisterForNtnSignalStrengthChanged(SUB_ID, callback);
+        sendNtnSignalStrengthChangedEvent(NTN_SIGNAL_STRENGTH_GREAT, null);
+        processAllMessages();
+        assertFalse(waitForForEvents(
+                semaphore, 1, "testRegisterForNtnSignalStrengthChanged"));
+        /* Even if all listeners are unregistered, the cache is updated with the latest value when a
+         new value event occurs. */
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_GREAT, SATELLITE_RESULT_SUCCESS);
+    }
+
+    @Test
+    public void testRegisterForNtnSignalStrengthChangedWithFeatureFlagDisabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+
+        Semaphore semaphore = new Semaphore(0);
+        final NtnSignalStrength[] signalStrength = new NtnSignalStrength[1];
+        INtnSignalStrengthCallback callback =
+                new INtnSignalStrengthCallback.Stub() {
+                    @Override
+                    public void onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength) {
+                        logd("onNtnSignalStrengthChanged: ntnSignalStrength="
+                                + ntnSignalStrength);
+                        try {
+                            signalStrength[0] = ntnSignalStrength;
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onNtnSignalStrengthChanged: Got exception in releasing "
+                                    + "semaphore, ex=" + ex);
+                        }
+                    }
+                };
+
+        verifyRegisterForNtnSignalStrengthChanged(SUB_ID, callback,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+
+        setUpResponseForRequestIsSatelliteSupported(false,
+                SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_NOT_SUPPORTED);
+        verifyRegisterForNtnSignalStrengthChanged(SUB_ID, callback,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+        setUpResponseForRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE,
+                SATELLITE_RESULT_SUCCESS);
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_NOT_SUPPORTED);
+        verifyRegisterForNtnSignalStrengthChanged(SUB_ID, callback,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+        verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE,
+                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+
+        @NtnSignalStrength.NtnSignalStrengthLevel int expectedNtnSignalStrengthLevel =
+                NTN_SIGNAL_STRENGTH_GOOD;
+        sendNtnSignalStrengthChangedEvent(expectedNtnSignalStrengthLevel, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 0, "testRegisterForNtnSignalStrengthChanged"));
+    }
+
+    @Test
+    public void testSendingNtnSignalStrengthWithFeatureEnabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        int expectedResult = SATELLITE_RESULT_SUCCESS;
+        // startSendingNtnSignalStrength() is requested when screen on event comes.
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        provisionSatelliteService();
+        setUpResponseForStartSendingNtnSignalStrength(expectedResult);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1))
+                .startSendingNtnSignalStrength(any(Message.class));
+
+        // requested again but ignored as expected and current state are matched.
+        setUpResponseForStartSendingNtnSignalStrength(expectedResult);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1))
+                .startSendingNtnSignalStrength(any(Message.class));
+
+        // stopSendingNtnSignalStrength() is requested when screen off event comes.
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForStopSendingNtnSignalStrength(expectedResult);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1))
+                .stopSendingNtnSignalStrength(any(Message.class));
+
+        // requested again but ignored as expected and current state are matched.
+        setUpResponseForStopSendingNtnSignalStrength(expectedResult);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1))
+                .stopSendingNtnSignalStrength(any(Message.class));
+
+        // startSendingNtnSignalStrength() is requested but received fail from the service.
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForStartSendingNtnSignalStrength(SATELLITE_RESULT_INVALID_MODEM_STATE);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1))
+                .startSendingNtnSignalStrength(any(Message.class));
+
+        /* stopSendingNtnSignalStrength() is ignored because startSendingNtnSignalStrength has
+           failed thus current state is stopSendingNtnSignalStrength */
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForStopSendingNtnSignalStrength(SATELLITE_RESULT_NO_RESOURCES);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, never())
+                .stopSendingNtnSignalStrength(any(Message.class));
+
+        // startSendingNtnSignalStrength() is requested and modem state is changed
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForStartSendingNtnSignalStrength(SATELLITE_RESULT_SUCCESS);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1))
+                .startSendingNtnSignalStrength(any(Message.class));
+
+        // stopSendingNtnSignalStrength() is failed as modem returns error
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForStopSendingNtnSignalStrength(SATELLITE_RESULT_NO_RESOURCES);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1))
+                .stopSendingNtnSignalStrength(any(Message.class));
+
+        // request stopSendingNtnSignalStrength() again and returns success
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForStopSendingNtnSignalStrength(SATELLITE_RESULT_SUCCESS);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, times(1))
+                .stopSendingNtnSignalStrength(any(Message.class));
+    }
+
+    @Test
+    public void testSendingNtnSignalStrengthWithFeatureDisabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+
+        int expectedResult = SATELLITE_RESULT_SUCCESS;
+        // startSendingNtnSignalStrength() is requested when screen on event comes.
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestIsSatelliteSupported(true, expectedResult);
+        setUpResponseForRequestIsSatelliteProvisioned(true, expectedResult);
+        verifySatelliteSupported(false, SATELLITE_RESULT_NOT_SUPPORTED);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+        setUpResponseForStartSendingNtnSignalStrength(expectedResult);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, never())
+                .startSendingNtnSignalStrength(any(Message.class));
+
+        // stopSendingNtnSignalStrength() is requested when screen off event comes.
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForStopSendingNtnSignalStrength(expectedResult);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(false);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, never())
+                .stopSendingNtnSignalStrength(any(Message.class));
+    }
+
+    @Test
+    public void testIsSatelliteSupportedViaCarrier() {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
+        assertFalse(mSatelliteControllerUT.isSatelliteSupportedViaCarrier());
+
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        assertFalse(mSatelliteControllerUT.isSatelliteSupportedViaCarrier());
+
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isSatelliteSupportedViaCarrier());
+    }
+
+    @Test
+    public void testCarrierEnabledSatelliteConnectionHysteresisTime() {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
+        assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mCarrierConfigBundle.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 1 * 60);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        mSatelliteControllerUT.elapsedRealtime = 0;
+        assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+
+        when(mServiceState.isUsingNonTerrestrialNetwork()).thenReturn(false);
+        when(mServiceState2.isUsingNonTerrestrialNetwork()).thenReturn(false);
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+
+        // Last satellite connected time of Phone2 should be 0
+        when(mServiceState2.isUsingNonTerrestrialNetwork()).thenReturn(true);
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        // 2 minutes later and hysteresis timeout is 1 minute
+        mSatelliteControllerUT.elapsedRealtime = 2 * 60 * 1000;
+        // But Phone2 is connected to NTN right now
+        assertTrue(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+
+        // Last satellite disconnected time of Phone2 should be 2 * 60 * 1000
+        when(mServiceState2.isUsingNonTerrestrialNetwork()).thenReturn(false);
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        // Current time (2) - last disconnected time (2) < hysteresis timeout (1)
+        assertTrue(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+
+        // Current time (4) - last disconnected time (2) > hysteresis timeout (1)
+        mSatelliteControllerUT.elapsedRealtime = 4 * 60 * 1000;
+        assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+    }
+
+    @Test
+    public void testRegisterForSatelliteCapabilitiesChangedWithFeatureFlagEnabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        Semaphore semaphore = new Semaphore(0);
+        final SatelliteCapabilities[] satelliteCapabilities = new SatelliteCapabilities[1];
+        ISatelliteCapabilitiesCallback callback =
+                new ISatelliteCapabilitiesCallback.Stub() {
+                    @Override
+                    public void onSatelliteCapabilitiesChanged(SatelliteCapabilities capabilities) {
+                        logd("onSatelliteCapabilitiesChanged: " + capabilities);
+                        try {
+                            satelliteCapabilities[0] = capabilities;
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onSatelliteCapabilitiesChanged: Got exception in releasing "
+                                    + "semaphore, ex=" + ex);
+                        }
+                    }
+                };
+
+        int errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
+
+        setUpResponseForRequestIsSatelliteSupported(false,
+                SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, errorCode);
+
+        resetSatelliteControllerUT();
+        provisionSatelliteService();
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
+        SatelliteCapabilities expectedCapabilities = mSatelliteCapabilities;
+        sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteCapabilitiesChanged"));
+        assertTrue(expectedCapabilities.equals(satelliteCapabilities[0]));
+
+        expectedCapabilities = mEmptySatelliteCapabilities;
+        sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteCapabilitiesChanged"));
+        assertTrue(expectedCapabilities.equals(satelliteCapabilities[0]));
+
+        mSatelliteControllerUT.unregisterForCapabilitiesChanged(SUB_ID, callback);
+        expectedCapabilities = mSatelliteCapabilities;
+        sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 0, "testRegisterForSatelliteCapabilitiesChanged"));
+    }
+
+    @Test
+    public void testRegisterForSatelliteCapabilitiesChangedWithFeatureFlagDisabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+
+        Semaphore semaphore = new Semaphore(0);
+        final SatelliteCapabilities[] satelliteCapabilities = new SatelliteCapabilities[1];
+        ISatelliteCapabilitiesCallback callback =
+                new ISatelliteCapabilitiesCallback.Stub() {
+                    @Override
+                    public void onSatelliteCapabilitiesChanged(SatelliteCapabilities capabilities) {
+                        logd("onSatelliteCapabilitiesChanged: " + capabilities);
+                        try {
+                            satelliteCapabilities[0] = capabilities;
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onSatelliteCapabilitiesChanged: Got exception in releasing "
+                                    + "semaphore, ex=" + ex);
+                        }
+                    }
+                };
+
+        int errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
+
+        setUpResponseForRequestIsSatelliteSupported(false,
+                SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_NOT_SUPPORTED);
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_NOT_SUPPORTED);
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
+
+        SatelliteCapabilities expectedCapabilities = mSatelliteCapabilities;
+        sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 0, "testRegisterForSatelliteCapabilitiesChanged"));
+    }
+
+    @Test
+    public void testSatelliteCommunicationRestrictionForEntitlement() throws Exception {
+        logd("testSatelliteCommunicationRestrictionForEntitlement");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+        SparseBooleanArray satelliteEnabledPerCarrier = new SparseBooleanArray();
+        replaceInstance(SatelliteController.class, "mSatelliteEntitlementStatusPerCarrier",
+                mSatelliteControllerUT, satelliteEnabledPerCarrier);
+
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(true, SATELLITE_RESULT_SUCCESS);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        Map<Integer, Set<Integer>> satelliteAttachRestrictionForCarrierArray = new HashMap<>();
+        satelliteAttachRestrictionForCarrierArray.put(SUB_ID, new HashSet<>());
+        satelliteAttachRestrictionForCarrierArray.get(SUB_ID).add(
+                SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT);
+        replaceInstance(SatelliteController.class, "mSatelliteAttachRestrictionForCarrierArray",
+                mSatelliteControllerUT, satelliteAttachRestrictionForCarrierArray);
+
+        // Verify call the requestSetSatelliteEnabledForCarrier to enable the satellite when
+        // satellite service is enabled by entitlement server.
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true, new ArrayList<>(),
+                mIIntegerConsumer);
+        processAllMessages();
+
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockSatelliteModemInterface, times(1))
+                .requestSetSatelliteEnabledForCarrier(anyInt(), eq(true), any(Message.class));
+
+        // Verify call the requestSetSatelliteEnabledForCarrier to disable the satellite when
+        // satellite service is disabled by entitlement server.
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        Map<Integer, Boolean> enabledForCarrierArrayPerSub = new HashMap<>();
+        enabledForCarrierArrayPerSub.put(SUB_ID, true);
+        replaceInstance(SatelliteController.class, "mIsSatelliteAttachEnabledForCarrierArrayPerSub",
+                mSatelliteControllerUT, enabledForCarrierArrayPerSub);
+        doReturn(mIsSatelliteServiceSupported)
+                .when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false, new ArrayList<>(),
+                mIIntegerConsumer);
+        processAllMessages();
+
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockSatelliteModemInterface, times(1))
+                .requestSetSatelliteEnabledForCarrier(anyInt(), eq(false), any(Message.class));
+    }
+
+    @Test
+    public void testPassSatellitePlmnToModemAfterUpdateSatelliteEntitlementStatus()
+            throws Exception {
+        logd("testPassSatellitePlmnToModemAfterUpdateSatelliteEntitlementStatus");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> overlayConfigPlmnList =  new ArrayList<>();
+        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
+                mSatelliteControllerUT, overlayConfigPlmnList);
+
+        // If the PlmnListPerCarrier and the overlay config plmn list are empty verify passing to
+        // the modem.
+        List<String> entitlementPlmnList = new ArrayList<>();
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
+                entitlementPlmnList, mIIntegerConsumer);
+
+        List<String> plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(
+                SUB_ID);
+        List<String> allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
+                plmnListPerCarrier, overlayConfigPlmnList);
+
+        assertEquals(new ArrayList<>(), plmnListPerCarrier);
+        assertEquals(new ArrayList<>(), allSatellitePlmnList);
+        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
+                eq(plmnListPerCarrier), eq(allSatellitePlmnList), any(Message.class));
+
+        // If the PlmnListPerCarrier and the overlay config plmn list are exist verify passing
+        // the modem.
+        entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "00103"}).toList();
+        overlayConfigPlmnList =
+                Arrays.stream(new String[]{"00101", "00102", "00104"}).toList();
+        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
+                mSatelliteControllerUT, overlayConfigPlmnList);
+
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true,
+                entitlementPlmnList, mIIntegerConsumer);
+
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
+        allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
+                plmnListPerCarrier, overlayConfigPlmnList);
+
+        assertEquals(entitlementPlmnList, plmnListPerCarrier);
+        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
+                eq(plmnListPerCarrier), eq(allSatellitePlmnList), any(Message.class));
+
+        // If the PlmnListPerCarrier and the overlay config plmn list are exist verify passing
+        // the modem.
+        reset(mMockSatelliteModemInterface);
+        entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "00103"}).toList();
+        Map<Integer, Map<String, Set<Integer>>>
+                satelliteServicesSupportedByCarriers = new HashMap<>();
+        List<String> carrierConfigPlmnList = Arrays.stream(new String[]{"00105", "00106"}).toList();
+        Map<String, Set<Integer>> plmnAndService = new HashMap<>();
+        plmnAndService.put(carrierConfigPlmnList.get(0), new HashSet<>(Arrays.asList(3, 5)));
+        plmnAndService.put(carrierConfigPlmnList.get(1), new HashSet<>(Arrays.asList(3)));
+        satelliteServicesSupportedByCarriers.put(SUB_ID, plmnAndService);
+        replaceInstance(SatelliteController.class, "mSatelliteServicesSupportedByCarriers",
+                mSatelliteControllerUT, satelliteServicesSupportedByCarriers);
+        overlayConfigPlmnList = Arrays.stream(new String[]{"00101", "00102", "00104"}).toList();
+        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
+                mSatelliteControllerUT, overlayConfigPlmnList);
+        List<String> mergedPlmnList = entitlementPlmnList;
+
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true,
+                entitlementPlmnList, mIIntegerConsumer);
+
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
+        allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
+                plmnListPerCarrier, overlayConfigPlmnList);
+
+        assertEquals(mergedPlmnList, plmnListPerCarrier);
+        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
+                eq(plmnListPerCarrier), eq(allSatellitePlmnList), any(Message.class));
+    }
+
+    @Test
+    public void testUpdatePlmnListPerCarrier() throws Exception {
+        logd("testUpdatePlmnListPerCarrier");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        replaceInstance(SatelliteController.class, "mSatelliteServicesSupportedByCarriers",
+                mSatelliteControllerUT, new HashMap<>());
+        SparseArray<List<String>> entitlementPlmnListPerCarrier = new SparseArray<>();
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, entitlementPlmnListPerCarrier);
+
+        // If the carrier config and the entitlement plmn list are empty, verify whether an empty
+        // list is returned.
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                new PersistableBundle());
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        List<String> plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(
+                SUB_ID);
+        assertEquals(new ArrayList<>(), plmnListPerCarrier);
+
+        // If the carrier config list is empty and the entitlement plmn list is exists, verify
+        // whether the entitlement list is returned.
+        entitlementPlmnListPerCarrier.clear();
+        List<String> entitlementPlmnList = Arrays.asList("00101", "00102", "00104");
+        entitlementPlmnListPerCarrier.put(SUB_ID, entitlementPlmnList);
+        List<String> expectedPlmnListPerCarrier = entitlementPlmnList;
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
+        assertEquals(expectedPlmnListPerCarrier, plmnListPerCarrier);
+
+        // If the carrier config list is exists and the entitlement plmn list is empty, verify
+        // whether the carrier config list is returned.
+        entitlementPlmnListPerCarrier.clear();
+        entitlementPlmnList = new ArrayList<>();
+        entitlementPlmnListPerCarrier.put(SUB_ID, entitlementPlmnList);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+        PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle();
+        List<String> carrierConfigPlmnList = Arrays.asList("00102", "00103", "00105");
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(0), new int[]{2});
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(1), new int[]{1, 3});
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(2), new int[]{2});
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                carrierSupportedSatelliteServicesPerProvider);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        expectedPlmnListPerCarrier = carrierConfigPlmnList;
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
+        assertEquals(expectedPlmnListPerCarrier.stream().sorted().toList(),
+                plmnListPerCarrier.stream().sorted().toList());
+
+
+        // If the carrier config and the entitlement plmn list are exist, verify whether the
+        // entitlement list is returned.
+        entitlementPlmnList = Arrays.asList("00101", "00102", "00104");
+        entitlementPlmnListPerCarrier.put(SUB_ID, entitlementPlmnList);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        expectedPlmnListPerCarrier = entitlementPlmnList;
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(
+                SUB_ID);
+        assertEquals(expectedPlmnListPerCarrier.stream().sorted().toList(),
+                plmnListPerCarrier.stream().sorted().toList());
+    }
+
+    @Test
+    public void testEntitlementStatus() throws Exception {
+        logd("testEntitlementStatus");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        SparseBooleanArray satelliteEnabledPerCarrier = new SparseBooleanArray();
+        replaceInstance(SatelliteController.class, "mSatelliteEntitlementStatusPerCarrier",
+                mSatelliteControllerUT, satelliteEnabledPerCarrier);
+
+        // Change SUB_ID's EntitlementStatus to true
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true, new ArrayList<>(),
+                mIIntegerConsumer);
+
+        assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID));
+        assertEquals(false, satelliteEnabledPerCarrier.get(SUB_ID1));
+
+        // Change SUB_ID1's EntitlementStatus to true
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID1, true, new ArrayList<>(),
+                mIIntegerConsumer);
+
+        assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID));
+        assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID1));
+
+        // Change SUB_ID's EntitlementStatus to false
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false, new ArrayList<>(),
+                mIIntegerConsumer);
+
+        assertEquals(false, satelliteEnabledPerCarrier.get(SUB_ID));
+        assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID1));
+    }
+
+    @Test
+    public void testUpdateRestrictReasonForEntitlementPerCarrier() throws Exception {
+        logd("testUpdateRestrictReasonForEntitlementPerCarrier");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        // Verify that the entitlement restriction reason is added before the entitlement query,
+        // When the Satellite entitlement status value read from DB is disabled.
+        doReturn("").when(mContext).getOpPackageName();
+        doReturn("").when(mContext).getAttributionTag();
+        doReturn("0").when(mMockSubscriptionManagerService).getSubscriptionProperty(anyInt(),
+                eq(SATELLITE_ENTITLEMENT_STATUS), anyString(), anyString());
+        doReturn(new ArrayList<>()).when(
+                mMockSubscriptionManagerService).getSatelliteEntitlementPlmnList(anyInt());
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        Set<Integer> restrictionSet =
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
+        assertEquals(1, restrictionSet.size());
+        assertTrue(restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT));
+    }
+
+    @Test
+    public void testUpdateEntitlementPlmnListPerCarrier() throws Exception {
+        logd("testUpdateEntitlementPlmnListPerCarrier");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        // If the Satellite entitlement plmn list read from the DB is empty and carrier config
+        // plmn list also is empty , check whether an empty list is returned when calling
+        // getSatellitePlmnsForCarrier before the entitlement query.
+        doReturn(new ArrayList<>()).when(
+                mMockSubscriptionManagerService).getSatelliteEntitlementPlmnList(anyInt());
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        replaceInstance(SatelliteController.class, "mSatelliteServicesSupportedByCarriers",
+                mSatelliteControllerUT, new HashMap<>());
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        assertEquals(new ArrayList<>(), mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID));
+
+        // If the Satellite entitlement plmn list read from the DB is valid and carrier config
+        // plmn list is empty, check whether valid entitlement plmn list is returned
+        // when calling getSatellitePlmnsForCarrier before the entitlement query.
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> expectedSatelliteEntitlementPlmnList = Arrays.asList("123456,12560");
+        doReturn(expectedSatelliteEntitlementPlmnList).when(
+                mMockSubscriptionManagerService).getSatelliteEntitlementPlmnList(anyInt());
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        assertEquals(expectedSatelliteEntitlementPlmnList,
+                mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID));
+
+        // If the Satellite entitlement plmn list read from the DB is valid and carrier config
+        // plmn list is valid, check whether valid entitlement plmn list is returned when
+        // calling getSatellitePlmnsForCarrier before the entitlement query.
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle();
+        List<String> carrierConfigPlmnList = Arrays.asList("00102", "00103", "00105");
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(0), new int[]{2});
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(1), new int[]{1, 3});
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(2), new int[]{2});
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                carrierSupportedSatelliteServicesPerProvider);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        assertEquals(expectedSatelliteEntitlementPlmnList,
+                mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID));
+
+        // If the Satellite entitlement plmn list read from the DB is empty and carrier config
+        // plmn list is valid, check whether valid carrier config plmn list is returned when
+        // calling getSatellitePlmnsForCarrier before the entitlement query.
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        doReturn(new ArrayList<>()).when(
+                mMockSubscriptionManagerService).getSatelliteEntitlementPlmnList(anyInt());
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        assertEquals(carrierConfigPlmnList.stream().sorted().toList(),
+                mSatelliteControllerUT.getSatellitePlmnsForCarrier(
+                        SUB_ID).stream().sorted().toList());
+    }
+
+    @Test
+    public void testHandleEventServiceStateChanged() throws Exception {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        // Do nothing when the satellite is not connected
+        doReturn(false).when(mServiceState).isUsingNonTerrestrialNetwork();
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        assertEquals(false,
+                mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false));
+        verify(mMockNotificationManager, never()).notifyAsUser(anyString(), anyInt(), any(), any());
+
+        // Check sending a system notification when the satellite is connected
+        doReturn(true).when(mServiceState).isUsingNonTerrestrialNetwork();
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        verify(mMockNotificationManager, times(1)).notifyAsUser(anyString(), anyInt(), any(),
+                any());
+        assertEquals(true,
+                mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false));
+
+        // Check don't display again after displayed already a system notification.
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        verify(mMockNotificationManager, times(1)).notifyAsUser(anyString(), anyInt(), any(),
+                any());
+    }
+
+    @Test
+    public void testRequestSatelliteEnabled_timeout() {
+        mIsSatelliteEnabledSemaphore.drainPermits();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+
+        // Successfully disable satellite
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Time out to enable satellite
+        ArgumentCaptor<Message> enableSatelliteResponse = ArgumentCaptor.forClass(Message.class);
+        mIIntegerConsumerResults.clear();
+        setUpNoResponseForRequestSatelliteEnabled(true, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(true), eq(false),
+                enableSatelliteResponse.capture());
+
+        clearInvocations(mMockSatelliteModemInterface);
+        moveTimeForward(TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_MODEM_TIMEOUT, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(false), eq(false), any(
+                Message.class));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Send the response for the above request to enable satellite. SatelliteController should
+        // ignore the event
+        Message response = enableSatelliteResponse.getValue();
+        AsyncResult.forMessage(response, null, null);
+        response.sendToTarget();
+        processAllMessages();
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Successfully enable satellite
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+
+        // Time out to disable satellite
+        ArgumentCaptor<Message> disableSatelliteResponse = ArgumentCaptor.forClass(Message.class);
+        mIIntegerConsumerResults.clear();
+        clearInvocations(mMockSatelliteModemInterface);
+        setUpNoResponseForRequestSatelliteEnabled(false, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(false), eq(false),
+                disableSatelliteResponse.capture());
+
+        clearInvocations(mMockSatelliteModemInterface);
+        moveTimeForward(TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_MODEM_TIMEOUT, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
+                anyBoolean(), any(Message.class));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Send the response for the above request to disable satellite. SatelliteController should
+        // ignore the event
+        response = disableSatelliteResponse.getValue();
+        AsyncResult.forMessage(response, null, null);
+        response.sendToTarget();
+        processAllMessages();
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
     }
 
     private void resetSatelliteControllerUTEnabledState() {
         logd("resetSatelliteControllerUTEnabledState");
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         doNothing().when(mMockSatelliteModemInterface)
                 .setSatelliteServicePackageName(anyString());
         mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService");
         processAllMessages();
 
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         sendProvisionedStateChangedEvent(true, null);
         processAllMessages();
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
     }
 
     private void resetSatelliteControllerUT() {
@@ -1611,7 +3145,7 @@
         processAllMessages();
 
         // Reset all cached states
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         doNothing().when(mMockSatelliteModemInterface)
                 .setSatelliteServicePackageName(anyString());
         mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService");
@@ -1620,11 +3154,11 @@
 
     private void resetSatelliteControllerUTToSupportedAndProvisionedState() {
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
-        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         sendProvisionedStateChangedEvent(true, null);
         processAllMessages();
-        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
     }
 
     private void resetSatelliteControllerUTToOffAndProvisionedState() {
@@ -1632,7 +3166,7 @@
         // Clean up pending resources and move satellite controller to OFF state.
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_UNAVAILABLE, null);
         processAllMessages();
-        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
     }
 
     private void resetSatelliteControllerUTToOnAndProvisionedState() {
@@ -1640,29 +3174,30 @@
         setRadioPower(true);
         processAllMessages();
 
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
     }
 
     private void setUpResponseForRequestIsSatelliteEnabled(boolean isSatelliteEnabled,
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
-            AsyncResult.forMessage(message, isSatelliteEnabled, exception);
+            int[] enabled = new int[] {isSatelliteEnabled ? 1 : 0};
+            AsyncResult.forMessage(message, enabled, exception);
             message.sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface).requestIsSatelliteEnabled(any(Message.class));
     }
 
     private void setUpResponseForRequestIsSatelliteSupported(
-            boolean isSatelliteSupported, @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            boolean isSatelliteSupported, @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
@@ -1673,8 +3208,8 @@
     }
 
     private void setUpResponseForRequestIsSatelliteAllowedForCurrentLocation(
-            boolean isSatelliteAllowed, @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            boolean isSatelliteAllowed, @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
@@ -1686,8 +3221,8 @@
     }
 
     private void setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
@@ -1699,10 +3234,10 @@
     }
 
     private void setUpResponseForRequestTimeForNextSatelliteVisibility(
-            int satelliteVisibilityTime, @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            int satelliteVisibilityTime, @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
-        int[] visibilityTime = new int[] {satelliteVisibilityTime};
+        int[] visibilityTime = new int[]{satelliteVisibilityTime};
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
             AsyncResult.forMessage(message, visibilityTime, exception);
@@ -1713,8 +3248,8 @@
     }
 
     private void setUpNullResponseForRequestTimeForNextSatelliteVisibility(
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
@@ -1726,10 +3261,10 @@
     }
 
     private void setUpResponseForRequestIsSatelliteProvisioned(
-            boolean isSatelliteProvisioned, @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            boolean isSatelliteProvisioned, @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
-        int[] provisioned = new int[] {isSatelliteProvisioned ? 1 : 0};
+        int[] provisioned = new int[]{isSatelliteProvisioned ? 1 : 0};
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
             AsyncResult.forMessage(message, provisioned, exception);
@@ -1739,8 +3274,8 @@
     }
 
     private void setUpResponseForRequestSatelliteEnabled(
-            boolean enabled, boolean demoMode, @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            boolean enabled, boolean demoMode, @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             if (exception == null && !enabled) {
@@ -1754,14 +3289,27 @@
                 .requestSatelliteEnabled(eq(enabled), eq(demoMode), any(Message.class));
     }
 
+    private void setUpResponseForRequestSetSatelliteEnabledForCarrier(
+            boolean enabled, @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[2];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface)
+                .requestSetSatelliteEnabledForCarrier(anyInt(), eq(enabled), any(Message.class));
+    }
+
     private void setUpNoResponseForRequestSatelliteEnabled(boolean enabled, boolean demoMode) {
         doNothing().when(mMockSatelliteModemInterface)
                 .requestSatelliteEnabled(eq(enabled), eq(demoMode), any(Message.class));
     }
 
     private void setUpResponseForProvisionSatelliteService(
-            String token, byte[] provisionData, @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            String token, byte[] provisionData, @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[2];
@@ -1778,8 +3326,8 @@
     }
 
     private void setUpResponseForDeprovisionSatelliteService(String token,
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[1];
@@ -1792,8 +3340,8 @@
 
     private void setUpResponseForRequestSatelliteCapabilities(
             SatelliteCapabilities satelliteCapabilities,
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
@@ -1803,6 +3351,20 @@
         }).when(mMockSatelliteModemInterface).requestSatelliteCapabilities(any(Message.class));
     }
 
+    private void setUpResponseForRequestNtnSignalStrength(
+            @NtnSignalStrength.NtnSignalStrengthLevel int ntnSignalStrengthLevel,
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, new NtnSignalStrength(ntnSignalStrengthLevel),
+                    exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).requestNtnSignalStrength(any(Message.class));
+    }
+
     private boolean waitForForEvents(
             Semaphore semaphore, int expectedNumberOfEvents, String caller) {
         for (int i = 0; i < expectedNumberOfEvents; i++) {
@@ -1820,8 +3382,8 @@
     }
 
     private void setUpNullResponseForRequestSatelliteCapabilities(
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
@@ -1832,29 +3394,51 @@
     }
 
     private void setUpResponseForStartSatelliteTransmissionUpdates(
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
             AsyncResult.forMessage(message, null, exception);
             message.sendToTarget();
             return null;
-        }).when(mMockPointingAppController).startSatelliteTransmissionUpdates(any(Message.class),
-                any());
+        }).when(mMockPointingAppController).startSatelliteTransmissionUpdates(any(Message.class));
     }
 
     private void setUpResponseForStopSatelliteTransmissionUpdates(
-            @SatelliteManager.SatelliteError int error) {
-        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[0];
             AsyncResult.forMessage(message, null, exception);
             message.sendToTarget();
             return null;
-        }).when(mMockPointingAppController).stopSatelliteTransmissionUpdates(any(Message.class),
-                any());
+        }).when(mMockPointingAppController).stopSatelliteTransmissionUpdates(any(Message.class));
+    }
+
+    private void setUpResponseForStartSendingNtnSignalStrength(
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).startSendingNtnSignalStrength(any(Message.class));
+    }
+
+    private void setUpResponseForStopSendingNtnSignalStrength(
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).stopSendingNtnSignalStrength(any(Message.class));
     }
 
     private boolean waitForRequestIsSatelliteSupportedResult(int expectedNumberOfEvents) {
@@ -1878,7 +3462,7 @@
             try {
                 if (!mSatelliteAllowedSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
                     loge("Timeout to receive "
-                            + "requestIsSatelliteCommunicationAllowedForCurrentLocation()"
+                            + "requestIsCommunicationAllowedForCurrentLocation()"
                             + " callback");
                     return false;
                 }
@@ -1967,6 +3551,22 @@
         return true;
     }
 
+    private boolean waitForRequestNtnSignalStrengthResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mRequestNtnSignalStrengthSemaphore.tryAcquire(TIMEOUT,
+                        TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive requestNtnSignalStrength() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("requestNtnSignalStrength: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
     private boolean waitForIIntegerConsumerResult(int expectedNumberOfEvents) {
         for (int i = 0; i < expectedNumberOfEvents; i++) {
             try {
@@ -1991,6 +3591,16 @@
         assertEquals(supported, mQueriedSatelliteSupported);
     }
 
+    private void verifySatelliteSupported(TestSatelliteController satelliteController,
+            boolean supported, int expectedErrorCode) {
+        mSatelliteSupportSemaphore.drainPermits();
+        satelliteController.requestIsSatelliteSupported(SUB_ID, mSatelliteSupportReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestIsSatelliteSupportedResult(1));
+        assertEquals(expectedErrorCode, mQueriedSatelliteSupportedResultCode);
+        assertEquals(supported, mQueriedSatelliteSupported);
+    }
+
     private void verifySatelliteEnabled(boolean enabled, int expectedErrorCode) {
         mIsSatelliteEnabledSemaphore.drainPermits();
         mSatelliteControllerUT.requestIsSatelliteEnabled(SUB_ID, mIsSatelliteEnabledReceiver);
@@ -2010,6 +3620,17 @@
         assertEquals(provisioned, mQueriedIsSatelliteProvisioned);
     }
 
+    private void verifyRequestNtnSignalStrength(
+            @NtnSignalStrength.NtnSignalStrengthLevel int signalStrengthLevel,
+            int expectedErrorCode) {
+        mRequestNtnSignalStrengthSemaphore.drainPermits();
+        mSatelliteControllerUT.requestNtnSignalStrength(SUB_ID, mRequestNtnSignalStrengthReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestNtnSignalStrengthResult(1));
+        assertEquals(expectedErrorCode, mQueriedNtnSignalStrengthResultCode);
+        assertEquals(signalStrengthLevel, mQueriedNtnSignalStrengthLevel);
+    }
+
     private void sendProvisionedStateChangedEvent(boolean provisioned, Throwable exception) {
         Message msg = mSatelliteControllerUT.obtainMessage(
                 26 /* EVENT_SATELLITE_PROVISION_STATE_CHANGED */);
@@ -2024,10 +3645,97 @@
         msg.sendToTarget();
     }
 
+    private void sendNtnSignalStrengthChangedEvent(
+            @NtnSignalStrength.NtnSignalStrengthLevel int ntnSignalStrengthLevel,
+            Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                34 /* EVENT_NTN_SIGNAL_STRENGTH_CHANGED */);
+        msg.obj = new AsyncResult(null, new NtnSignalStrength(ntnSignalStrengthLevel),
+                exception);
+        msg.sendToTarget();
+    }
+
+    private void sendCmdStartSendingNtnSignalStrengthChangedEvent(boolean shouldReport) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                35 /* CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING */);
+        msg.obj = new AsyncResult(null, shouldReport, null);
+        msg.sendToTarget();
+    }
+
+    private void sendStartSendingNtnSignalStrengthChangedEvent(
+            @NtnSignalStrength.NtnSignalStrengthLevel int ntnSignalStrengthLevel,
+            Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                36 /* EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE */);
+        msg.obj = new AsyncResult(null, new NtnSignalStrength(ntnSignalStrengthLevel),
+                exception);
+        msg.sendToTarget();
+    }
+
+    private void sendServiceStateChangedEvent() {
+        mSatelliteControllerUT.obtainMessage(37 /* EVENT_SERVICE_STATE_CHANGED */).sendToTarget();
+    }
+
+    private void sendSatelliteCapabilitiesChangedEvent(SatelliteCapabilities capabilities,
+            Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                38 /* EVENT_SATELLITE_CAPABILITIES_CHANGED */);
+        msg.obj = new AsyncResult(null, capabilities, exception);
+        msg.sendToTarget();
+    }
+
     private void setRadioPower(boolean on) {
         mSimulatedCommands.setRadioPower(on, false, false, null);
     }
 
+    private void verifyRegisterForNtnSignalStrengthChanged(int subId,
+            INtnSignalStrengthCallback callback, int expectedError) {
+        if (expectedError == SATELLITE_RESULT_SUCCESS) {
+            try {
+                mSatelliteControllerUT.registerForNtnSignalStrengthChanged(subId, callback);
+            } catch (RemoteException ex) {
+                throw new AssertionError();
+            }
+        } else {
+            RemoteException ex = assertThrows(RemoteException.class,
+                    () -> mSatelliteControllerUT.registerForNtnSignalStrengthChanged(subId,
+                            callback));
+            assertTrue("The cause is not IllegalStateException",
+                    ex.getCause() instanceof IllegalStateException);
+        }
+    }
+
+    private void provisionSatelliteService() {
+        String mText = "This is test provision data.";
+        byte[] testProvisionData = mText.getBytes();
+        ICancellationSignal cancelRemote;
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
+                SATELLITE_RESULT_SUCCESS);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        assertNotNull(cancelRemote);
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+    }
+
+    private void deprovisionSatelliteService() {
+        mIIntegerConsumerResults.clear();
+        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+    }
+
     private static void loge(String message) {
         Rlog.e(TAG, message);
     }
@@ -2176,10 +3884,12 @@
     private static class TestSatelliteController extends SatelliteController {
         public boolean setSettingsKeyForSatelliteModeCalled = false;
         public boolean allRadiosDisabled = true;
+        public long elapsedRealtime = 0;
         public int satelliteModeSettingValue = SATELLITE_MODE_ENABLED_FALSE;
 
-        TestSatelliteController(Context context, Looper looper) {
-            super(context, looper);
+        TestSatelliteController(
+                Context context, Looper looper, @NonNull FeatureFlags featureFlags) {
+            super(context, looper, featureFlags);
             logd("Constructing TestSatelliteController");
         }
 
@@ -2199,5 +3909,17 @@
         protected boolean areAllRadiosDisabled() {
             return allRadiosDisabled;
         }
+
+        @Override
+        protected int getSupportedNtnRadioTechnology() {
+            int ntRadioTechnology = super.getSupportedNtnRadioTechnology();
+            logd("getCurrentNtnRadioTechnology: val=" + ntRadioTechnology);
+            return ntRadioTechnology;
+        }
+
+        @Override
+        protected long getElapsedRealtime() {
+            return elapsedRealtime;
+        }
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
index 5e11297..20b33eb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
@@ -16,8 +16,15 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
+import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT;
+import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
+import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -26,30 +33,35 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
-import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.Looper;
+import android.os.OutcomeReceiver;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.telecom.Call;
 import android.telecom.Connection;
 import android.telephony.BinderCacheManager;
-import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.SatelliteManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
+import com.android.internal.R;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -59,8 +71,10 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -74,48 +88,56 @@
     private static final String TAG = "SatelliteSOSMessageRecommenderTest";
     private static final long TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = 500;
     private static final int PHONE_ID = 0;
+    private static final int PHONE_ID2 = 1;
     private static final String CALL_ID = "CALL_ID";
     private static final String WRONG_CALL_ID = "WRONG_CALL_ID";
+    private static final String DEFAULT_SATELLITE_MESSAGING_PACKAGE = "android.com.google.default";
+    private static final String DEFAULT_SATELLITE_MESSAGING_CLASS =
+            "android.com.google.default.SmsMmsApp";
+    private static final String DEFAULT_HANDOVER_INTENT_ACTION =
+            "android.com.vendor.action.EMERGENCY_MESSAGING";
     private TestSatelliteController mTestSatelliteController;
     private TestImsManager mTestImsManager;
-
-    @Mock
-    private Context mMockContext;
     @Mock
     private Resources mResources;
     @Mock
     private ImsManager.MmTelFeatureConnectionFactory mMmTelFeatureConnectionFactory;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     private TestConnection mTestConnection;
     private TestSOSMessageRecommender mTestSOSMessageRecommender;
+    private ServiceState mServiceState2;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         MockitoAnnotations.initMocks(this);
 
-        when(mMockContext.getMainLooper()).thenReturn(Looper.myLooper());
-        when(mMockContext.getResources()).thenReturn(mResources);
-        when(mResources.getString(com.android.internal.R.string.config_satellite_service_package))
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getString(R.string.config_satellite_service_package))
                 .thenReturn("");
-        when(mMockContext.getSystemServiceName(CarrierConfigManager.class))
-                .thenReturn("CarrierConfigManager");
-        when(mMockContext.getSystemService(CarrierConfigManager.class))
-                .thenReturn(mCarrierConfigManager);
-        when(mMockContext.getSystemServiceName(SubscriptionManager.class))
-                .thenReturn("SubscriptionManager");
-        when(mMockContext.getSystemService(SubscriptionManager.class))
-                .thenReturn(mSubscriptionManager);
-        mTestSatelliteController = new TestSatelliteController(mMockContext,
-                Looper.myLooper());
+        when(mResources.getString(R.string.config_satellite_emergency_handover_intent_action))
+                .thenReturn(DEFAULT_HANDOVER_INTENT_ACTION);
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        mTestSatelliteController = new TestSatelliteController(mContext,
+                Looper.myLooper(), mFeatureFlags);
         mTestImsManager = new TestImsManager(
-                mMockContext, PHONE_ID, mMmTelFeatureConnectionFactory, null, null, null);
+                mContext, PHONE_ID, mMmTelFeatureConnectionFactory, null, null, null);
         mTestConnection = new TestConnection(CALL_ID);
+        mPhones = new Phone[] {mPhone, mPhone2};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        mServiceState2 = Mockito.mock(ServiceState.class);
         when(mPhone.getServiceState()).thenReturn(mServiceState);
-        mTestSOSMessageRecommender = new TestSOSMessageRecommender(Looper.myLooper(),
+        when(mPhone.getPhoneId()).thenReturn(PHONE_ID);
+        when(mPhone2.getServiceState()).thenReturn(mServiceState2);
+        when(mPhone2.getPhoneId()).thenReturn(PHONE_ID2);
+        mTestSOSMessageRecommender = new TestSOSMessageRecommender(mContext, Looper.myLooper(),
                 mTestSatelliteController, mTestImsManager,
                 TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
         when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState2.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mPhone.isImsRegistered()).thenReturn(false);
+        when(mPhone2.isImsRegistered()).thenReturn(false);
     }
 
     @After
@@ -124,18 +146,131 @@
     }
 
     @Test
-    public void testTimeoutBeforeEmergencyCallEnd() {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+    public void testTimeoutBeforeEmergencyCallEnd_T911() {
+        testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
+                DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS,
+                DEFAULT_HANDOVER_INTENT_ACTION);
+    }
+
+    @Test
+    public void testTimeoutBeforeEmergencyCallEnd_SOS_WithValidHandoverAppConfigured() {
+        String satelliteHandoverApp =
+                "android.com.vendor.message;android.com.vendor.message.SmsApp";
+        when(mResources.getString(R.string.config_oem_enabled_satellite_sos_handover_app))
+                .thenReturn(satelliteHandoverApp);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
+        testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
+                "android.com.vendor.message", "android.com.vendor.message.SmsApp",
+                DEFAULT_HANDOVER_INTENT_ACTION);
+    }
+
+    @Test
+    public void testTimeoutBeforeEmergencyCallEnd_SOS_WithInValidHandoverAppConfigured() {
+        String satelliteHandoverApp =
+                "android.com.vendor.message;android.com.vendor.message.SmsApp;abc";
+        when(mResources.getString(R.string.config_oem_enabled_satellite_sos_handover_app))
+                .thenReturn(satelliteHandoverApp);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
+        testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
+                DEFAULT_HANDOVER_INTENT_ACTION);
+    }
+
+    @Test
+    public void testTimeoutBeforeEmergencyCallEnd_SOS_WithoutHandoverAppConfigured() {
+        when(mResources.getString(R.string.config_oem_enabled_satellite_sos_handover_app))
+                .thenReturn("");
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
+        testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
+                DEFAULT_HANDOVER_INTENT_ACTION);
+    }
+
+    private void testTimeoutBeforeEmergencyCallEnd(int expectedHandoverType,
+            String expectedPackageName, String expectedClassName, String expectedAction) {
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
+        // Move Location service to emergency mode
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                mTestConnection.getTelecomCallId(), Connection.STATE_DIALING);
+        processAllMessages();
+        assertNotNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+
+        // Wait for the timeout to expires
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback.onResult(true);
+        moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        processAllMessages();
+        if (TextUtils.isEmpty(expectedPackageName) || TextUtils.isEmpty(expectedClassName)) {
+            assertTrue(mTestConnection.isEventWithoutLaunchIntentSent(
+                    TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE, expectedHandoverType));
+        } else {
+            assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
+                    expectedHandoverType, expectedPackageName, expectedClassName, expectedAction));
+        }
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+    }
+
+    @Test
+    public void testTimeoutBeforeEmergencyCallEnd_EventDisplayEmergencyMessageNotSent() {
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
+        mTestSatelliteController.setIsSatelliteViaOemProvisioned(false);
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        processAllMessages();
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+
+        // Move Location service to emergency mode
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                mTestConnection.getTelecomCallId(), Connection.STATE_DIALING);
+        processAllMessages();
+        assertNotNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+
+        // Wait for the timeout to expires
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback.onResult(true);
+        moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        processAllMessages();
+        assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+    }
+
+    @Test
+    public void testTimeoutBeforeEmergencyCallEnd_T911_FromNotConnectedToConnected() {
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
+        mTestSatelliteController.isOemEnabledSatelliteSupported = false;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        processAllMessages();
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+
+        // Move Location service to emergency mode
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                mTestConnection.getTelecomCallId(), Connection.STATE_DIALING);
+        processAllMessages();
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(true);
         // Wait for the timeout to expires
         moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
         processAllMessages();
-
-        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
-        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
-        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
+                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
+                DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS,
+                DEFAULT_HANDOVER_INTENT_ACTION));
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        mTestSatelliteController.isOemEnabledSatelliteSupported = true;
     }
 
     @Test
@@ -150,42 +285,67 @@
 
     @Test
     public void testImsRegistrationStateChangedBeforeTimeout() {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
-
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
-        mTestImsManager.sendImsRegistrationStateChangedEvent(true);
+        when(mPhone.isImsRegistered()).thenReturn(true);
+        mTestImsManager.sendImsRegistrationStateChangedEvent(0, true);
         processAllMessages();
 
-        assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0);
 
-        mTestImsManager.sendImsRegistrationStateChangedEvent(false);
+        when(mPhone.isImsRegistered()).thenReturn(false);
+        when(mPhone2.isImsRegistered()).thenReturn(true);
+        mTestImsManager.sendImsRegistrationStateChangedEvent(1, true);
+        processAllMessages();
+        assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0);
+
+        when(mPhone2.isImsRegistered()).thenReturn(false);
+        mTestImsManager.sendImsRegistrationStateChangedEvent(1, false);
         processAllMessages();
         assertEquals(2, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+
+        // Move Location service to emergency mode
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                mTestConnection.getTelecomCallId(), Connection.STATE_DIALING);
+        processAllMessages();
+        assertNotNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
         // Wait for the timeout to expires
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback.onResult(true);
         moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
         processAllMessages();
 
-        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
-        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
+                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
+                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
     }
 
     @Test
     public void testSatelliteProvisionStateChangedBeforeTimeout() {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
 
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
 
         mTestSatelliteController.sendProvisionStateChangedEvent(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false);
@@ -193,72 +353,77 @@
 
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
 
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertRegisterForStateChangedEventsTriggered(mPhone, 2, 2, 2);
+        assertRegisterForStateChangedEventsTriggered(mPhone, 2, 4, 2);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 2, 4, 2);
+
+        // Move Location service to emergency mode
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                mTestConnection.getTelecomCallId(), Connection.STATE_DIALING);
+        processAllMessages();
+        assertNotNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
         mTestSatelliteController.sendProvisionStateChangedEvent(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true);
 
         // Wait for the timeout to expires
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback.onResult(true);
         moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
         processAllMessages();
 
-        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
+                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
+                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertUnregisterForStateChangedEventsTriggered(mPhone, 2, 2, 2);
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 2, 4, 2);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 2, 4, 2);
     }
 
     @Test
     public void testEmergencyCallRedialBeforeTimeout() {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
-
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
-        Phone newPhone = Mockito.mock(Phone.class);
-        when(newPhone.getServiceState()).thenReturn(mServiceState);
-        when(newPhone.isImsRegistered()).thenReturn(false);
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, newPhone);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
-
-        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        /**
-         * Since {@link SatelliteSOSMessageRecommender} always uses
-         * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} when registering for provision state
-         * changed events with {@link SatelliteController}, registerForProvisionCount does
-         * not depend on Phone.
-         * <p>
-         * Since we use a single mocked ImsManager instance, registerForImsCount does not depend on
-         * Phone.
-         */
-        assertRegisterForStateChangedEventsTriggered(newPhone, 2, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
 
-        // Wait for the timeout to expires
+        // Move Location service to emergency mode
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                mTestConnection.getTelecomCallId(), Connection.STATE_DIALING);
+        processAllMessages();
+        assertNotNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+
+        // Wait for the timeout to expires and satellite access restriction checking result
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback.onResult(true);
         moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
         processAllMessages();
 
-        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
-        /**
-         * Since {@link SatelliteSOSMessageRecommender} always uses
-         * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} when unregistering for provision
-         * state changed events with {@link SatelliteController}, unregisterForProvisionCount does
-         * not depend on Phone.
-         * <p>
-         * Since we use a single mocked ImsManager instance, unregisterForImsCount does not depend
-         * on Phone.
-         */
-        assertUnregisterForStateChangedEventsTriggered(newPhone, 2, 2, 1);
+        assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
+                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
+                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
     }
 
     @Test
@@ -287,50 +452,62 @@
 
     @Test
     public void testOnEmergencyCallConnectionStateChangedWithWrongCallId() {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
 
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
 
         mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
                 WRONG_CALL_ID, Connection.STATE_ACTIVE);
         processAllMessages();
 
-        assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
     }
 
     @Test
     public void testSatelliteNotAllowedInCurrentLocation() {
-        mTestSatelliteController.setIsSatelliteCommunicationAllowed(false);
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        processAllMessages();
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+
+        // Move Location service to emergency mode
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                mTestConnection.getTelecomCallId(), Connection.STATE_DIALING);
+        processAllMessages();
+        assertNotNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback.onResult(false);
+        moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
         processAllMessages();
 
-        /**
-         * We should have registered for the state change events abd started the timer when
-         * receiving the event onEmergencyCallStarted. After getting the callback for the result of
-         * the request requestIsSatelliteCommunicationAllowedForCurrentLocation, the resources
-         * should be cleaned up.
-         */
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
-        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
     }
 
     @Test
     public void testOnEmergencyCallStarted() {
         SatelliteController satelliteController = new SatelliteController(
-                mMockContext, Looper.myLooper());
+                mContext, Looper.myLooper(), mFeatureFlags);
         TestSOSMessageRecommender testSOSMessageRecommender = new TestSOSMessageRecommender(
+                mContext,
                 Looper.myLooper(),
                 satelliteController, mTestImsManager,
                 TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
-        testSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        testSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
 
         assertFalse(testSOSMessageRecommender.isTimerStarted());
@@ -339,51 +516,76 @@
 
     private void testStopTrackingCallBeforeTimeout(
             @Connection.ConnectionState int connectionState) {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
 
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
 
         mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(CALL_ID, connectionState);
         processAllMessages();
 
-        assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
     }
 
     private void testCellularServiceStateChangedBeforeTimeout(
             @ServiceState.RegState int availableServiceState,
             @ServiceState.RegState int unavailableServiceState) {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
-
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
 
-        mTestSOSMessageRecommender.sendServiceStateChangedEvent(availableServiceState);
+        when(mServiceState.getState()).thenReturn(availableServiceState);
+        mTestSOSMessageRecommender.sendServiceStateChangedEvent();
         processAllMessages();
-
-        assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 0, 0, 0);
 
-        mTestSOSMessageRecommender.sendServiceStateChangedEvent(unavailableServiceState);
+        when(mServiceState.getState()).thenReturn(unavailableServiceState);
+        when(mServiceState2.getState()).thenReturn(availableServiceState);
+        mTestSOSMessageRecommender.sendServiceStateChangedEvent();
+        processAllMessages();
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+
+        when(mServiceState2.getState()).thenReturn(unavailableServiceState);
+        mTestSOSMessageRecommender.sendServiceStateChangedEvent();
         processAllMessages();
         assertEquals(2, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+
+        // Move Location service to emergency mode
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                mTestConnection.getTelecomCallId(), Connection.STATE_DIALING);
+        processAllMessages();
+        assertNotNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
         // Wait for the timeout to expires
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback.onResult(true);
         moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
         processAllMessages();
 
-        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
-        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
+                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
+                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
     }
 
     private void assertRegisterForStateChangedEventsTriggered(
@@ -412,8 +614,10 @@
                 mProvisionStateChangedCallbacks;
         private int mRegisterForSatelliteProvisionStateChangedCalls = 0;
         private int mUnregisterForSatelliteProvisionStateChangedCalls = 0;
-        private boolean mIsSatelliteProvisioned = true;
-        private boolean mIsSatelliteCommunicationAllowed = true;
+        private boolean mIsSatelliteViaOemProvisioned = true;
+        private boolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime = true;
+        public boolean isOemEnabledSatelliteSupported = true;
+        public boolean isCarrierEnabledSatelliteSupported = true;
 
         /**
          * Create a SatelliteController to act as a backend service of
@@ -421,30 +625,36 @@
          *
          * @param context The Context for the SatelliteController.
          */
-        protected TestSatelliteController(Context context, Looper looper) {
-            super(context, looper);
+        protected TestSatelliteController(
+                Context context, Looper looper, FeatureFlags featureFlags) {
+            super(context, looper, featureFlags);
             mProvisionStateChangedCallbacks = new HashMap<>();
         }
 
         @Override
-        public Boolean isSatelliteProvisioned() {
-            return mIsSatelliteProvisioned;
+        public Boolean isSatelliteViaOemProvisioned() {
+            return mIsSatelliteViaOemProvisioned;
         }
 
         @Override
-        public boolean isSatelliteSupported() {
-            return true;
+        public boolean isSatelliteSupportedViaOem() {
+            return isOemEnabledSatelliteSupported;
         }
 
         @Override
-        @SatelliteManager.SatelliteError public int registerForSatelliteProvisionStateChanged(
+        public boolean isSatelliteSupportedViaCarrier() {
+            return isCarrierEnabledSatelliteSupported;
+        }
+
+        @Override
+        @SatelliteManager.SatelliteResult public int registerForSatelliteProvisionStateChanged(
                 int subId, @NonNull ISatelliteProvisionStateCallback callback) {
             mRegisterForSatelliteProvisionStateChangedCalls++;
             Set<ISatelliteProvisionStateCallback> perSubscriptionCallbacks =
                     mProvisionStateChangedCallbacks.getOrDefault(subId, new HashSet<>());
             perSubscriptionCallbacks.add(callback);
             mProvisionStateChangedCallbacks.put(subId, perSubscriptionCallbacks);
-            return SatelliteManager.SATELLITE_ERROR_NONE;
+            return SatelliteManager.SATELLITE_RESULT_SUCCESS;
         }
 
         @Override
@@ -459,16 +669,18 @@
         }
 
         @Override
-        public void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId,
-                @NonNull ResultReceiver result) {
-            Bundle bundle = new Bundle();
-            bundle.putBoolean(SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED,
-                    mIsSatelliteCommunicationAllowed);
-            result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+        public boolean isSatelliteConnectedViaCarrierWithinHysteresisTime() {
+            return mIsSatelliteConnectedViaCarrierWithinHysteresisTime;
         }
 
-        public void setIsSatelliteCommunicationAllowed(boolean allowed) {
-            mIsSatelliteCommunicationAllowed = allowed;
+        @Override
+        protected int getEnforcedEmergencyCallToSatelliteHandoverType() {
+            return INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
+        }
+
+        public void setSatelliteConnectedViaCarrierWithinHysteresisTime(
+                boolean connectedViaCarrier) {
+            mIsSatelliteConnectedViaCarrierWithinHysteresisTime = connectedViaCarrier;
         }
 
         public int getRegisterForSatelliteProvisionStateChangedCalls() {
@@ -479,8 +691,12 @@
             return mUnregisterForSatelliteProvisionStateChangedCalls;
         }
 
+        public void setIsSatelliteViaOemProvisioned(boolean provisioned) {
+            mIsSatelliteViaOemProvisioned = provisioned;
+        }
+
         public void sendProvisionStateChangedEvent(int subId, boolean provisioned) {
-            mIsSatelliteProvisioned = provisioned;
+            mIsSatelliteViaOemProvisioned = provisioned;
             Set<ISatelliteProvisionStateCallback> perSubscriptionCallbacks =
                     mProvisionStateChangedCallbacks.get(subId);
             if (perSubscriptionCallbacks != null) {
@@ -497,7 +713,7 @@
 
     private static class TestImsManager extends ImsManager {
 
-        private final Set<RegistrationManager.RegistrationCallback> mCallbacks;
+        private final List<RegistrationManager.RegistrationCallback> mCallbacks;
         private int mAddRegistrationCallbackCalls = 0;
         private int mRemoveRegistrationListenerCalls = 0;
 
@@ -508,7 +724,7 @@
                 SubscriptionManagerProxy subManagerProxy, SettingsProxy settingsProxy,
                 BinderCacheManager binderCacheManager) {
             super(context, phoneId, factory, subManagerProxy, settingsProxy, binderCacheManager);
-            mCallbacks = new HashSet<>();
+            mCallbacks = new ArrayList<>();
         }
 
         @Override
@@ -525,7 +741,9 @@
             }
 
             callback.setExecutor(executor);
-            mCallbacks.add(callback);
+            if (!mCallbacks.contains(callback)) {
+                mCallbacks.add(callback);
+            }
         }
 
         @Override
@@ -546,20 +764,26 @@
             return mRemoveRegistrationListenerCalls;
         }
 
-        public void sendImsRegistrationStateChangedEvent(boolean registered) {
+        public void sendImsRegistrationStateChangedEvent(int callbackIndex, boolean registered) {
+            if (callbackIndex < 0 || callbackIndex >= mCallbacks.size()) {
+                throw new IndexOutOfBoundsException("sendImsRegistrationStateChangedEvent: invalid"
+                        + "callbackIndex=" + callbackIndex
+                        + ", mCallbacks.size=" + mCallbacks.size());
+            }
+            RegistrationManager.RegistrationCallback callback = mCallbacks.get(callbackIndex);
             if (registered) {
-                for (RegistrationManager.RegistrationCallback callback : mCallbacks) {
-                    callback.onRegistered(null);
-                }
+                callback.onRegistered(null);
             } else {
-                for (RegistrationManager.RegistrationCallback callback : mCallbacks) {
-                    callback.onUnregistered(null);
-                }
+                callback.onUnregistered(null);
             }
         }
     }
 
     private static class TestSOSMessageRecommender extends SatelliteSOSMessageRecommender {
+        public OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>
+                isSatelliteAllowedCallback = null;
+        private ComponentName mSmsAppComponent = new ComponentName(
+                DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS);
 
         /**
          * Create an instance of SatelliteSOSMessageRecommender.
@@ -571,9 +795,23 @@
          *                            null only in unit tests.
          * @param timeoutMillis       The timeout duration of the timer.
          */
-        TestSOSMessageRecommender(Looper looper, SatelliteController satelliteController,
-                ImsManager imsManager, long timeoutMillis) {
-            super(looper, satelliteController, imsManager, timeoutMillis);
+        TestSOSMessageRecommender(Context context, Looper looper,
+                SatelliteController satelliteController, ImsManager imsManager,
+                long timeoutMillis) {
+            super(context, looper, satelliteController, imsManager, timeoutMillis);
+        }
+
+        @Override
+        protected ComponentName getDefaultSmsApp() {
+            return mSmsAppComponent;
+        }
+
+        @Override
+        protected void requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                @NonNull OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback) {
+            logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: callback="
+                    + callback);
+            isSatelliteAllowedCallback = callback;
         }
 
         public boolean isTimerStarted() {
@@ -584,28 +822,66 @@
             return mCountOfTimerStarted;
         }
 
-        public void sendServiceStateChangedEvent(@ServiceState.RegState int state) {
-            ServiceState serviceState = new ServiceState();
-            serviceState.setState(state);
-            sendMessage(obtainMessage(EVENT_CELLULAR_SERVICE_STATE_CHANGED,
-                    new AsyncResult(null, serviceState, null)));
+        public void sendServiceStateChangedEvent() {
+            sendMessage(obtainMessage(EVENT_SERVICE_STATE_CHANGED));
         }
     }
 
     private static class TestConnection extends Connection {
-        private final Set<String> mSentEvents;
+        private String mSentEvent = null;
+        private Bundle mExtras = null;
         TestConnection(String callId) {
             setTelecomCallId(callId);
-            mSentEvents = new HashSet<>();
         }
 
         @Override
         public void sendConnectionEvent(String event, Bundle extras) {
-            mSentEvents.add(event);
+            mSentEvent = event;
+            mExtras = extras;
+        }
+
+        public boolean isEventSent(String event, int handoverType, String packageName,
+                String className, String action) {
+            if (mSentEvent == null || mExtras == null) {
+                return false;
+            }
+
+            PendingIntent pendingIntent = mExtras.getParcelable(
+                    EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT, PendingIntent.class);
+            Intent intent = pendingIntent.getIntent();
+            if (!TextUtils.equals(event, mSentEvent) || handoverType != mExtras.getInt(
+                    EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE)
+                    || !TextUtils.equals(packageName, intent.getComponent().getPackageName())
+                    || !TextUtils.equals(className, intent.getComponent().getClassName())
+                    || !TextUtils.equals(action, intent.getAction())) {
+                return false;
+            }
+            return true;
+        }
+
+        public boolean isEventWithoutLaunchIntentSent(String event, int handoverType) {
+            if (mSentEvent == null || mExtras == null) {
+                return false;
+            }
+
+            PendingIntent pendingIntent = mExtras.getParcelable(
+                    EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT, PendingIntent.class);
+            if (!TextUtils.equals(event, mSentEvent) || handoverType != mExtras.getInt(
+                    EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE) || pendingIntent != null) {
+                return false;
+            }
+
+            return true;
         }
 
         public boolean isEventSent(String event) {
-            return mSentEvents.contains(event);
+            if (mSentEvent == null) {
+                return false;
+            }
+            if (!TextUtils.equals(event, mSentEvent)) {
+                return false;
+            }
+            return true;
         }
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
index ba1fb9e..28874df 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
@@ -17,7 +17,6 @@
 package com.android.internal.telephony.satellite;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.os.PersistableBundle;
@@ -33,8 +32,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -57,58 +55,6 @@
     }
 
     @Test
-    public void testParseSupportedSatelliteServicesFromStringArray() {
-        // Parse correct format input string
-        int[] expectedServices1 = {2, 3};
-        int[] expectedServices2 = {3};
-        String[] supportedServicesStrArr1 = {"10011:2,3", "10112:3"};
-        Map<String, Set<Integer>> supportedServiceMap =
-                SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServicesStrArr1);
-
-        assertTrue(supportedServiceMap.containsKey("10011"));
-        Set<Integer> supportedServices = supportedServiceMap.get("10011");
-        assertTrue(Arrays.equals(expectedServices1,
-                supportedServices.stream()
-                        .mapToInt(Integer::intValue)
-                        .toArray()));
-
-        assertTrue(supportedServiceMap.containsKey("10112"));
-        supportedServices = supportedServiceMap.get("10112");
-        assertTrue(Arrays.equals(expectedServices2,
-                supportedServices.stream()
-                        .mapToInt(Integer::intValue)
-                        .toArray()));
-
-        // Parse correct mixed with incorrect format input string
-        String[] supportedServicesStrArr2 = {"10011:2,3,1xy", "10112:3,70", "10012:"};
-        supportedServiceMap = SatelliteServiceUtils.parseSupportedSatelliteServices(
-                supportedServicesStrArr2);
-
-        assertTrue(supportedServiceMap.containsKey("10011"));
-        supportedServices = supportedServiceMap.get("10011");
-        assertTrue(Arrays.equals(expectedServices1,
-                supportedServices.stream()
-                        .mapToInt(Integer::intValue)
-                        .toArray()));
-
-        assertTrue(supportedServiceMap.containsKey("10112"));
-        supportedServices = supportedServiceMap.get("10112");
-        assertTrue(Arrays.equals(expectedServices2,
-                supportedServices.stream()
-                        .mapToInt(Integer::intValue)
-                        .toArray()));
-
-        assertTrue(supportedServiceMap.containsKey("10012"));
-        assertTrue(supportedServiceMap.get("10012").isEmpty());
-
-        // Parse an empty input string
-        String[] supportedServicesStrArr3 = {};
-        supportedServiceMap = SatelliteServiceUtils.parseSupportedSatelliteServices(
-                supportedServicesStrArr3);
-        assertTrue(supportedServiceMap.isEmpty());
-    }
-
-    @Test
     public void testParseSupportedSatelliteServicesFromPersistableBundle() {
         PersistableBundle supportedServicesBundle = new PersistableBundle();
         String plmn1 = "10101";
@@ -153,46 +99,11 @@
     }
 
     @Test
-    public void testMergeSupportedSatelliteServices() {
-        String plmn1 = "00101";
-        String plmn2 = "00102";
-        String plmn3 = "00103";
-
-        Integer[] providerSupportedServicesForPlmn1 = {1, 2, 3};
-        Integer[] providerSupportedServicesForPlmn2 = {3, 4};
-        Map<String, Set<Integer>> providerSupportedServicesMap = new HashMap<>();
-        providerSupportedServicesMap.put(
-                plmn1, new HashSet<>(Arrays.asList(providerSupportedServicesForPlmn1)));
-        providerSupportedServicesMap.put(
-                plmn2, new HashSet<>(Arrays.asList(providerSupportedServicesForPlmn2)));
-
-        Integer[] carrierSupportedServicesForPlmn2 = {3};
-        Integer[] carrierSupportedServicesForPlmn3 = {1, 3, 4};
-        Map<String, Set<Integer>> carrierSupportedServicesMap = new HashMap<>();
-        carrierSupportedServicesMap.put(
-                plmn2, new HashSet<>(Arrays.asList(carrierSupportedServicesForPlmn2)));
-        carrierSupportedServicesMap.put(
-                plmn3, new HashSet<>(Arrays.asList(carrierSupportedServicesForPlmn3)));
-
-        // {@code plmn1} is present in only provider support services.
-        int[] expectedSupportedServicesForPlmn1 = {1, 2, 3};
-        // Intersection of {3,4} and {3}.
-        int[] expectedSupportedServicesForPlmn2 = {3};
-        Map<String, Set<Integer>> supportedServicesMap =
-                SatelliteServiceUtils.mergeSupportedSatelliteServices(
-                        providerSupportedServicesMap, carrierSupportedServicesMap);
-
-        assertEquals(2, supportedServicesMap.size());
-        assertTrue(supportedServicesMap.containsKey(plmn1));
-        assertTrue(supportedServicesMap.containsKey(plmn2));
-        assertFalse(supportedServicesMap.containsKey(plmn3));
-        assertTrue(Arrays.equals(expectedSupportedServicesForPlmn1,
-                supportedServicesMap.get(plmn1).stream()
-                        .mapToInt(Integer::intValue)
-                        .toArray()));
-        assertTrue(Arrays.equals(expectedSupportedServicesForPlmn2,
-                supportedServicesMap.get(plmn2).stream()
-                        .mapToInt(Integer::intValue)
-                        .toArray()));
+    public void testMergeStrLists() {
+        List<String> l1 = Arrays.asList("1", "2", "2");
+        List<String> l2 = Arrays.asList("1", "3", "3");
+        List<String> expectedMergedList = Arrays.asList("1", "2", "3");
+        List<String> mergedList = SatelliteServiceUtils.mergeStrLists(l1, l2);
+        assertEquals(expectedMergedList, mergedList);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
index 3ccf512..a1c2cfc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
@@ -18,21 +18,28 @@
 
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.Looper;
 import android.os.Message;
-import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.SatelliteManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -57,40 +64,55 @@
 @TestableLooper.RunWithLooper
 public class SatelliteSessionControllerTest extends TelephonyTest {
     private static final String TAG = "SatelliteSessionControllerTest";
-    private static final long TEST_SATELLITE_STAY_AT_LISTENING_MILLIS = 200;
-    private static final long EVENT_PROCESSING_TIME_MILLIS = 100;
+    private static final int TEST_SATELLITE_TIMEOUT_MILLIS = 200;
+    private static final int EVENT_PROCESSING_TIME_MILLIS = 100;
 
     private static final String STATE_UNAVAILABLE = "UnavailableState";
     private static final String STATE_POWER_OFF = "PowerOffState";
     private static final String STATE_IDLE = "IdleState";
     private static final String STATE_TRANSFERRING = "TransferringState";
     private static final String STATE_LISTENING = "ListeningState";
+    private static final String STATE_NOT_CONNECTED = "NotConnectedState";
+    private static final String STATE_CONNECTED = "ConnectedState";
 
     private TestSatelliteModemInterface mSatelliteModemInterface;
     private TestSatelliteSessionController mTestSatelliteSessionController;
-    private TestSatelliteStateCallback mTestSatelliteStateCallback;
+    private TestSatelliteModemStateCallback mTestSatelliteModemStateCallback;
 
-    @Mock
-    private SatelliteController mSatelliteController;
+    @Mock private SatelliteController mMockSatelliteController;
+    @Mock private DatagramReceiver mMockDatagramReceiver;
+    @Mock private DatagramDispatcher mMockDatagramDispatcher;
+    @Mock private DatagramController mMockDatagramController;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         MockitoAnnotations.initMocks(this);
 
+        replaceInstance(DatagramReceiver.class, "sInstance", null,
+                mMockDatagramReceiver);
+        replaceInstance(DatagramDispatcher.class, "sInstance", null,
+                mMockDatagramDispatcher);
+        replaceInstance(SatelliteController.class, "sInstance", null,
+                mMockSatelliteController);
+        replaceInstance(DatagramController.class, "sInstance", null,
+                mMockDatagramController);
+
+        Resources resources = mContext.getResources();
+        when(resources.getInteger(anyInt())).thenReturn(TEST_SATELLITE_TIMEOUT_MILLIS);
+
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(false);
         mSatelliteModemInterface = new TestSatelliteModemInterface(
-                mContext, mSatelliteController, Looper.myLooper());
+                mContext, mMockSatelliteController, Looper.myLooper());
         mTestSatelliteSessionController = new TestSatelliteSessionController(mContext,
-                Looper.myLooper(), true, mSatelliteModemInterface,
-                TEST_SATELLITE_STAY_AT_LISTENING_MILLIS,
-                TEST_SATELLITE_STAY_AT_LISTENING_MILLIS);
+                Looper.myLooper(), true, mSatelliteModemInterface);
         processAllMessages();
 
-        mTestSatelliteStateCallback = new TestSatelliteStateCallback();
+        mTestSatelliteModemStateCallback = new TestSatelliteModemStateCallback();
         mTestSatelliteSessionController.registerForSatelliteModemStateChanged(
-                mTestSatelliteStateCallback);
+                mTestSatelliteModemStateCallback);
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
     }
 
     @After
@@ -105,8 +127,7 @@
          * state.
          */
         TestSatelliteSessionController sessionController1 = new TestSatelliteSessionController(
-                mContext, Looper.myLooper(), false,
-                mSatelliteModemInterface, 100, 100);
+                mContext, Looper.myLooper(), false, mSatelliteModemInterface);
         assertNotNull(sessionController1);
         processAllMessages();
         assertEquals(STATE_UNAVAILABLE, sessionController1.getCurrentStateName());
@@ -115,8 +136,7 @@
          * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
          */
         TestSatelliteSessionController sessionController2 = new TestSatelliteSessionController(
-                mContext, Looper.myLooper(), true,
-                mSatelliteModemInterface, 100, 100);
+                mContext, Looper.myLooper(), true, mSatelliteModemInterface);
         assertNotNull(sessionController2);
         processAllMessages();
         assertEquals(STATE_POWER_OFF, sessionController2.getCurrentStateName());
@@ -129,8 +149,7 @@
          * state.
          */
         TestSatelliteSessionController sessionController = new TestSatelliteSessionController(
-                mContext, Looper.myLooper(), false,
-                mSatelliteModemInterface, 100, 100);
+                mContext, Looper.myLooper(), false, mSatelliteModemInterface);
         assertNotNull(sessionController);
         processAllMessages();
         assertEquals(STATE_UNAVAILABLE, sessionController.getCurrentStateName());
@@ -158,7 +177,7 @@
 
         // SatelliteSessionController should move to IDLE state after the modem is powered on.
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
@@ -168,7 +187,7 @@
 
         // SatelliteSessionController should move back to POWER_OFF state.
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
@@ -178,7 +197,7 @@
 
         // SatelliteSessionController should move to IDLE state after radio is turned on.
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
@@ -188,7 +207,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
@@ -200,7 +219,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to IDLE state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
@@ -212,7 +231,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
@@ -224,7 +243,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to LISTENING state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
         assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName());
         assertEquals(1, mSatelliteModemInterface.getListeningEnabledCount());
@@ -237,7 +256,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertEquals(1, mSatelliteModemInterface.getListeningDisabledCount());
@@ -249,7 +268,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to LISTENING state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
         assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName());
         assertEquals(2, mSatelliteModemInterface.getListeningEnabledCount());
@@ -262,7 +281,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertEquals(2, mSatelliteModemInterface.getListeningDisabledCount());
@@ -275,7 +294,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to IDLE state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
@@ -287,7 +306,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
@@ -298,18 +317,18 @@
         processAllMessages();
 
         // SatelliteSessionController should move to LISTENING state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
         assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName());
         assertEquals(3, mSatelliteModemInterface.getListeningEnabledCount());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
         // Wait for timeout
-        moveTimeForward(TEST_SATELLITE_STAY_AT_LISTENING_MILLIS);
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
         processAllMessages();
 
         // SatelliteSessionController should move to IDLE state after timeout
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
         assertEquals(3, mSatelliteModemInterface.getListeningDisabledCount());
@@ -322,7 +341,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
@@ -334,7 +353,7 @@
         processAllMessages();
 
         // SatelliteSessionController should stay at TRANSFERRING state.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
@@ -346,7 +365,7 @@
 
         // SatelliteSessionController should stay at TRANSFERRING state instead of moving to IDLE
         // state.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
@@ -357,7 +376,7 @@
         processAllMessages();
 
         // SatelliteSessionController should stay at TRANSFERRING state.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
@@ -369,7 +388,7 @@
 
         // SatelliteSessionController should stay at TRANSFERRING state instead of moving to IDLE
         // state.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
@@ -379,14 +398,424 @@
 
         // SatelliteSessionController should move to POWER_OFF state.
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
     }
 
+    @Test
+    public void testStateTransitionForNbIot() {
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+
+        /**
+         * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+         */
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(false);
+
+        // Power on the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+        // SatelliteSessionController should stay at NOT_CONNECTED state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+
+        setupDatagramTransferringState(true);
+
+        // Power off the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        // SatelliteSessionController should move back to POWER_OFF state.
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        clearInvocations(mMockDatagramController);
+
+        // Power on the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after radio is turned on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // The datagram sending event should be ignored.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Satellite modem is connected to a satellite network.
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        clearInvocations(mMockDatagramController);
+
+        // Sending datagrams failed
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams again
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        clearInvocations(mMockDatagramController);
+
+        // Sending datagrams is successful and done.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Start receiving datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        clearInvocations(mMockDatagramController);
+
+        // Receiving datagrams is successful and done.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Start receiving datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        clearInvocations(mMockDatagramController);
+
+        // Receiving datagrams is successful and done.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Satellite modem is connected to a satellite network.
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Satellite modem is disconnected from the satellite network.
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Satellite modem is connected to a satellite network.
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Power off the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        // SatelliteSessionController should move to POWER_OFF state.
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        clearInvocations(mMockDatagramController);
+
+        // Power on the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Satellite modem is connected to a satellite network.
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Set up error response for the request to disable cellular scanning
+        mSatelliteModemInterface.setErrorCode(SatelliteManager.SATELLITE_RESULT_MODEM_ERROR);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should stay at IDLE state because it failed to disable
+        // cellular scanning.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+
+        mSatelliteModemInterface.setErrorCode(SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
+        // Power off the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        // SatelliteSessionController should move to POWER_OFF
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Power on the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state because NB-IOT inactivity timer has
+        // timed out.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Power off the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        // SatelliteSessionController should move to POWER_OFF
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Power on the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Start sending datagrams and the NB-IOT inactivity timer should be stopped.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should stay at NOT_CONNECTED state because.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Transferring datagram failed because satellite failed to connect to a satellite network.
+        // The NB-IOT inactivity timer should be started.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state because NB-IOT inactivity timer has
+        // timed out.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+    }
+
+    private void setupDatagramTransferringState(boolean isTransferring) {
+        when(mMockDatagramController.isSendingInIdleState()).thenReturn(isTransferring);
+        when(mMockDatagramController.isPollingInIdleState()).thenReturn(isTransferring);
+    }
+
     private static class TestSatelliteModemInterface extends SatelliteModemInterface {
         private final AtomicInteger mListeningEnabledCount = new AtomicInteger(0);
         private final AtomicInteger mListeningDisabledCount = new AtomicInteger(0);
+        @SatelliteManager.SatelliteResult
+        private int mErrorCode = SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
         TestSatelliteModemInterface(@NonNull Context context,
                 SatelliteController satelliteController, @NonNull Looper looper) {
@@ -411,6 +840,14 @@
             else mListeningDisabledCount.incrementAndGet();
         }
 
+        @Override
+        public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
+                @Nullable Message message) {
+            if (message != null) {
+                sendMessageWithResult(message, null, mErrorCode);
+            }
+        }
+
         public int getListeningEnabledCount() {
             return mListeningEnabledCount.get();
         }
@@ -418,28 +855,32 @@
         public int getListeningDisabledCount() {
             return mListeningDisabledCount.get();
         }
+
+        public void setErrorCode(@SatelliteManager.SatelliteResult int errorCode) {
+            mErrorCode = errorCode;
+        }
     }
 
     private static class TestSatelliteSessionController extends SatelliteSessionController {
         TestSatelliteSessionController(Context context, Looper looper, boolean isSatelliteSupported,
-                SatelliteModemInterface satelliteModemInterface,
-                long satelliteStayAtListeningFromSendingMillis,
-                long satelliteStayAtListeningFromReceivingMillis) {
-            super(context, looper, isSatelliteSupported, satelliteModemInterface,
-                    satelliteStayAtListeningFromSendingMillis,
-                    satelliteStayAtListeningFromReceivingMillis);
+                SatelliteModemInterface satelliteModemInterface) {
+            super(context, looper, isSatelliteSupported, satelliteModemInterface);
         }
 
-        public String getCurrentStateName() {
+        String getCurrentStateName() {
             return getCurrentState().getName();
         }
 
-        public boolean isSendingTriggeredDuringTransferringState() {
+        boolean isSendingTriggeredDuringTransferringState() {
             return mIsSendingTriggeredDuringTransferringState.get();
         }
+
+        boolean isNbIotInactivityTimerStarted() {
+            return hasMessages(EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT);
+        }
     }
 
-    private static class TestSatelliteStateCallback extends ISatelliteStateCallback.Stub {
+    private static class TestSatelliteModemStateCallback extends ISatelliteModemStateCallback.Stub {
         private final AtomicInteger mModemState = new AtomicInteger(
                 SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         private final Semaphore mSemaphore = new Semaphore(0);
@@ -474,7 +915,7 @@
     }
 
     private static void assertSuccessfulModemStateChangedCallback(
-            TestSatelliteStateCallback callback,
+            TestSatelliteModemStateCallback callback,
             @SatelliteManager.SatelliteModemState int expectedModemState) {
         boolean successful = callback.waitUntilResult();
         assertTrue(successful);
@@ -482,7 +923,7 @@
     }
 
     private static void assertModemStateChangedCallbackNotCalled(
-            TestSatelliteStateCallback callback) {
+            TestSatelliteModemStateCallback callback) {
         boolean successful = callback.waitUntilResult();
         assertFalse(successful);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java
new file mode 100644
index 0000000..8841c7a
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.security;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.telephony.CellularIdentifierDisclosure;
+
+import com.android.internal.telephony.TestExecutorService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+import java.util.concurrent.TimeUnit;
+
+public class CellularIdentifierDisclosureNotifierTest {
+
+    // 15 minutes and 100 milliseconds. Can be used to advance time in a test executor far enough
+    // to (hopefully, if the code is behaving) close a disclosure window.
+    private static final long WINDOW_CLOSE_ADVANCE_MILLIS = (15 * 60 * 1000) + 100;
+    private static final int SUB_ID_1 = 1;
+    private static final int SUB_ID_2 = 2;
+    private CellularIdentifierDisclosure mDislosure;
+    private CellularNetworkSecuritySafetySource mSafetySource;
+    private Context mContext;
+    private InOrder mInOrder;
+
+    @Before
+    public void setUp() {
+        mDislosure =
+                new CellularIdentifierDisclosure(
+                        CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        false);
+        mSafetySource = mock(CellularNetworkSecuritySafetySource.class);
+        mInOrder = inOrder(mSafetySource);
+    }
+
+    @Test
+    public void testInitializeDisabled() {
+        TestExecutorService executor = new TestExecutorService();
+        CellularIdentifierDisclosureNotifier notifier =
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+
+        assertFalse(notifier.isEnabled());
+        verify(mSafetySource, never()).setIdentifierDisclosureIssueEnabled(any(), anyBoolean());
+    }
+
+    @Test
+    public void testDisableAddDisclosureNop() {
+        TestExecutorService executor = new TestExecutorService();
+        CellularIdentifierDisclosureNotifier notifier =
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+
+        assertFalse(notifier.isEnabled());
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        verify(mSafetySource, never())
+                .setIdentifierDisclosure(any(), anyInt(), anyInt(), any(), any());
+    }
+
+    @Test
+    public void testAddDisclosureEmergencyNop() {
+        TestExecutorService executor = new TestExecutorService();
+        CellularIdentifierDisclosureNotifier notifier =
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+        CellularIdentifierDisclosure emergencyDisclosure =
+                new CellularIdentifierDisclosure(
+                        CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        true);
+
+        notifier.enable(mContext);
+        notifier.addDisclosure(mContext, SUB_ID_1, emergencyDisclosure);
+
+        assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        verify(mSafetySource, never())
+                .setIdentifierDisclosure(any(), anyInt(), anyInt(), any(), any());
+    }
+
+    @Test
+    public void testAddDisclosureCountIncrements() {
+        TestExecutorService executor = new TestExecutorService();
+        CellularIdentifierDisclosureNotifier notifier =
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+
+        notifier.enable(mContext);
+
+        for (int i = 0; i < 3; i++) {
+            notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        }
+
+        assertEquals(3, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(3), any(), any());
+    }
+
+    @Test
+    public void testSingleDisclosureStartAndEndTimesAreEqual() {
+        TestExecutorService executor = new TestExecutorService();
+        CellularIdentifierDisclosureNotifier notifier =
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+
+        notifier.enable(mContext);
+
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+
+        assertEquals(1, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        assertTrue(notifier.getFirstOpen(SUB_ID_1).equals(notifier.getCurrentEnd(SUB_ID_1)));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+    }
+
+    @Test
+    public void testMultipleDisclosuresTimeWindows() {
+        TestExecutorService executor = new TestExecutorService();
+        CellularIdentifierDisclosureNotifier notifier =
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+
+        notifier.enable(mContext);
+
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        try {
+            Thread.sleep(50);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+
+        assertEquals(2, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        assertTrue(notifier.getFirstOpen(SUB_ID_1).isBefore(notifier.getCurrentEnd(SUB_ID_1)));
+        verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+    }
+
+    @Test
+    public void testAddDisclosureThenWindowClose() {
+        TestExecutorService executor = new TestExecutorService();
+        CellularIdentifierDisclosureNotifier notifier =
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+
+        // One round of disclosures
+        notifier.enable(mContext);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        assertEquals(2, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());
+
+        // Window close should reset the counter
+        executor.advanceTime(WINDOW_CLOSE_ADVANCE_MILLIS);
+        assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
+
+        // A new disclosure should increment as normal
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        assertEquals(1, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+    }
+
+    @Test
+    public void testDisableClosesWindow() {
+        TestExecutorService executor = new TestExecutorService();
+        CellularIdentifierDisclosureNotifier notifier =
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+
+        // One round of disclosures
+        notifier.enable(mContext);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        assertEquals(2, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());
+
+        notifier.disable(mContext);
+        assertFalse(notifier.isEnabled());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosureIssueEnabled(any(), eq(false));
+
+        // We're disabled now so no disclosures should open the disclosure window
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        assertEquals(0, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        mInOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testMultipleSubIdsTrackedIndependently() {
+        TestExecutorService executor = new TestExecutorService();
+        CellularIdentifierDisclosureNotifier notifier =
+                new CellularIdentifierDisclosureNotifier(
+                        executor, 15, TimeUnit.MINUTES, mSafetySource);
+
+        notifier.enable(mContext);
+        for (int i = 0; i < 3; i++) {
+            notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+        }
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(2), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(3), any(), any());
+
+        for (int i = 0; i < 4; i++) {
+            notifier.addDisclosure(mContext, SUB_ID_2, mDislosure);
+        }
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_2), eq(1), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_2), eq(2), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_2), eq(3), any(), any());
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_2), eq(4), any(), any());
+
+        assertEquals(3, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        assertEquals(4, notifier.getCurrentDisclosureCount(SUB_ID_2));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureTest.java
new file mode 100644
index 0000000..327a1cb
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.security;
+
+import static android.telephony.CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMEI;
+import static android.telephony.CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI;
+import static android.telephony.CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST;
+import static android.telephony.CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.hardware.radio.network.CellularIdentifier;
+import android.hardware.radio.network.NasProtocolMessage;
+import android.os.Parcel;
+
+import com.android.internal.telephony.RILUtils;
+
+import org.junit.Test;
+
+public class CellularIdentifierDisclosureTest {
+
+    @Test
+    public void testEqualsAndHash() {
+        android.telephony.CellularIdentifierDisclosure disclosure =
+                new android.telephony.CellularIdentifierDisclosure(
+                        NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        false);
+
+        android.telephony.CellularIdentifierDisclosure anotherDislcosure =
+                new android.telephony.CellularIdentifierDisclosure(
+                        NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        false);
+        assertEquals(disclosure, anotherDislcosure);
+        assertEquals(disclosure.hashCode(), anotherDislcosure.hashCode());
+    }
+
+    @Test
+    public void testNotEqualsAndHash() {
+        android.telephony.CellularIdentifierDisclosure imsiDisclosure =
+                new android.telephony.CellularIdentifierDisclosure(
+                        NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        false);
+
+        android.telephony.CellularIdentifierDisclosure imeiDisclosure =
+                new android.telephony.CellularIdentifierDisclosure(
+                        NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CELLULAR_IDENTIFIER_IMEI,
+                        "001001",
+                        false);
+
+        assertNotEquals(imsiDisclosure, imeiDisclosure);
+        assertNotEquals(imsiDisclosure.hashCode(), imeiDisclosure.hashCode());
+    }
+
+    @Test
+    public void testGetters() {
+        android.telephony.CellularIdentifierDisclosure disclosure =
+                new android.telephony.CellularIdentifierDisclosure(
+                        NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        false);
+
+        assertEquals(NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, disclosure.getNasProtocolMessage());
+        assertEquals(CELLULAR_IDENTIFIER_IMSI, disclosure.getCellularIdentifier());
+        assertEquals(false, disclosure.isEmergency());
+        assertEquals("001001", disclosure.getPlmn());
+    }
+
+    @Test
+    public void testParcel() {
+        android.telephony.CellularIdentifierDisclosure disclosure =
+                new android.telephony.CellularIdentifierDisclosure(
+                        NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        false);
+
+        Parcel p = Parcel.obtain();
+        disclosure.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        android.telephony.CellularIdentifierDisclosure fromParcel =
+                android.telephony.CellularIdentifierDisclosure.CREATOR.createFromParcel(p);
+        assertEquals(disclosure, fromParcel);
+    }
+
+    @Test
+    public void testConvertCellularIdentifierDisclosure() {
+        android.hardware.radio.network.CellularIdentifierDisclosure aidlDisclsoure =
+                new android.hardware.radio.network.CellularIdentifierDisclosure();
+        aidlDisclsoure.plmn = "001001";
+        aidlDisclsoure.identifier = NasProtocolMessage.IDENTITY_RESPONSE;
+        aidlDisclsoure.protocolMessage = CellularIdentifier.IMEI;
+        aidlDisclsoure.isEmergency = true;
+
+        android.telephony.CellularIdentifierDisclosure expectedDisclosure =
+                new android.telephony.CellularIdentifierDisclosure(
+                        NAS_PROTOCOL_MESSAGE_IDENTITY_RESPONSE,
+                        CELLULAR_IDENTIFIER_IMEI,
+                        "001001",
+                        true);
+
+        assertEquals(
+                expectedDisclosure, RILUtils.convertCellularIdentifierDisclosure(aidlDisclsoure));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
new file mode 100644
index 0000000..76118c4
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.security;
+
+import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_ENCRYPTED;
+import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_NOTIFY_ENCRYPTED;
+import static com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.safetycenter.SafetySourceData;
+import android.util.Singleton;
+
+import com.android.internal.R;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.TestApplication;
+import com.android.internal.telephony.security.CellularNetworkSecuritySafetySource.SafetyCenterManagerWrapper;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.time.Instant;
+
+public final class CellularNetworkSecuritySafetySourceTest extends TelephonyTest {
+
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        // unmock ActivityManager to be able to register receiver, create real PendingIntents.
+        restoreInstance(Singleton.class, "mInstance", mIActivityManagerSingleton);
+        restoreInstance(ActivityManager.class, "IActivityManagerSingleton", null);
+
+        SubscriptionInfoInternal info0 = new SubscriptionInfoInternal.Builder()
+                .setId(0)
+                .setDisplayName("fake_name0")
+                .build();
+        doReturn(info0).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(0));
+        SubscriptionInfoInternal info1 = new SubscriptionInfoInternal.Builder()
+                .setId(1)
+                .setDisplayName("fake_name1")
+                .build();
+        doReturn(info1).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(1));
+
+        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.scNullCipherIssueEncryptedTitle, "fake %1$s");
+        mContextFixture.putResource(R.string.scNullCipherIssueEncryptedSummary, "fake");
+        mContextFixture.putResource(R.string.scIdentifierDisclosureIssueTitle, "fake");
+        mContextFixture.putResource(
+                R.string.scIdentifierDisclosureIssueSummary, "fake %1$d %2$tr %3$tr %4$s");
+        mContextFixture.putResource(R.string.scNullCipherIssueActionSettings, "fake");
+        mContextFixture.putResource(R.string.scNullCipherIssueActionLearnMore, "fake");
+
+        mSafetyCenterManagerWrapper = mock(SafetyCenterManagerWrapper.class);
+        doAnswer(inv -> getActivityPendingIntent(inv.getArgument(1)))
+                .when(mSafetyCenterManagerWrapper)
+                .getActivityPendingIntent(any(Context.class), any(Intent.class));
+
+        mSafetySource = new CellularNetworkSecuritySafetySource(mSafetyCenterManagerWrapper);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private PendingIntent getActivityPendingIntent(Intent intent) {
+        Context context = TestApplication.getAppContext();
+        return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    @Test
+    public void disableNullCipherIssue_nullData() {
+        mSafetySource.setNullCipherIssueEnabled(mContext, false);
+
+        verify(mSafetyCenterManagerWrapper, times(1)).setSafetySourceData(isNull());
+    }
+
+    @Test
+    public void enableNullCipherIssue_statusWithoutIssues() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+
+        verify(mSafetyCenterManagerWrapper, times(1)).setSafetySourceData(data.capture());
+        assertThat(data.getValue().getStatus()).isNotNull();
+        assertThat(data.getValue().getIssues()).isEmpty();
+    }
+
+    @Test
+    public void setNullCipherState_encrypted_statusWithoutIssue() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_ENCRYPTED);
+
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).isEmpty();
+    }
+
+    @Test
+    public void setNullCipherState_notifyEncrypted_statusWithIssue() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_ENCRYPTED);
+
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+    }
+
+    @Test
+    public void setNullCipherState_notifyNonEncrypted_statusWithIssue() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED);
+
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+    }
+
+    @Test
+    public void setNullCipherState_multipleNonEncrypted_statusWithTwoIssues() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED);
+        mSafetySource.setNullCipherState(mContext, 1, NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED);
+
+        verify(mSafetyCenterManagerWrapper, times(3)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(2).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(2).getIssues()).hasSize(2);
+    }
+
+    @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.
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, false);
+
+        verify(mSafetyCenterManagerWrapper, times(1)).setSafetySourceData(isNull());
+    }
+
+    @Test
+    public void enableIdentifierDisclosureIssue_enableTwice() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosure(mContext, 0, 12, Instant.now(), Instant.now());
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+
+        // Two invocations because the initial enablement and the subsequent disclosure result in
+        // updates to safety center
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        // When we're enabled, enabling again should not clear our issue list.
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+    }
+
+    @Test
+    public void enableIdentifierDisclosueIssue_statusWithoutIssues() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+
+        verify(mSafetyCenterManagerWrapper, times(1)).setSafetySourceData(data.capture());
+        assertThat(data.getValue().getStatus()).isNotNull();
+        assertThat(data.getValue().getIssues()).isEmpty();
+    }
+
+    @Test
+    public void setIdentifierDisclosure_singleDisclosure_statusWithIssue() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosure(mContext, 0, 12, Instant.now(), Instant.now());
+
+        verify(mSafetyCenterManagerWrapper, times(2)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+    }
+
+    @Test
+    public void setIdentifierDisclosure_multipleDisclosures_statusWithTwoIssues() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosure(mContext, 0, 12, Instant.now(), Instant.now());
+        mSafetySource.setIdentifierDisclosure(mContext, 1, 3, Instant.now(), Instant.now());
+
+        verify(mSafetyCenterManagerWrapper, times(3)).setSafetySourceData(data.capture());
+        assertThat(data.getAllValues().get(2).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(2).getIssues()).hasSize(2);
+    }
+
+    @Test
+    public void multipleIssuesKinds_statusWithTwoIssues() {
+        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());
+        assertThat(data.getAllValues().get(3).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(3).getIssues()).hasSize(2);
+    }
+}
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/SecurityAlgorithmUpdateTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/SecurityAlgorithmUpdateTest.java
new file mode 100644
index 0000000..bd50151
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/SecurityAlgorithmUpdateTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.security;
+
+import static android.telephony.SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_3G;
+import static android.telephony.SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP;
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128;
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2;
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96;
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA1;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.radio.network.ConnectionEvent;
+import android.hardware.radio.network.SecurityAlgorithm;
+import android.os.Parcel;
+import android.telephony.SecurityAlgorithmUpdate;
+
+import com.android.internal.telephony.RILUtils;
+
+import org.junit.Test;
+
+public final class SecurityAlgorithmUpdateTest {
+
+    @Test
+    public void testEqualsAndHash() {
+        SecurityAlgorithmUpdate update = new SecurityAlgorithmUpdate(
+                CONNECTION_EVENT_VOLTE_SIP, SECURITY_ALGORITHM_EEA2,
+                SECURITY_ALGORITHM_HMAC_SHA1_96, false);
+        SecurityAlgorithmUpdate sameUpdate = new SecurityAlgorithmUpdate(
+                CONNECTION_EVENT_VOLTE_SIP, SECURITY_ALGORITHM_EEA2,
+                SECURITY_ALGORITHM_HMAC_SHA1_96, false);
+
+        assertThat(update).isEqualTo(sameUpdate);
+        assertThat(update.hashCode()).isEqualTo(sameUpdate.hashCode());
+    }
+
+    @Test
+    public void testNotEqualsAndHash() {
+        SecurityAlgorithmUpdate update = new SecurityAlgorithmUpdate(
+                CONNECTION_EVENT_VOLTE_SIP, SECURITY_ALGORITHM_EEA2,
+                SECURITY_ALGORITHM_HMAC_SHA1_96, false);
+        SecurityAlgorithmUpdate sameUpdate = new SecurityAlgorithmUpdate(
+                CONNECTION_EVENT_VOLTE_SIP, SECURITY_ALGORITHM_EEA2,
+                SECURITY_ALGORITHM_HMAC_SHA1_96, true);
+
+        assertThat(update).isNotEqualTo(sameUpdate);
+        assertThat(update.hashCode()).isNotEqualTo(sameUpdate.hashCode());
+    }
+
+    @Test
+    public void testGetters() {
+        SecurityAlgorithmUpdate update = new SecurityAlgorithmUpdate(
+                CONNECTION_EVENT_VOLTE_SIP, SECURITY_ALGORITHM_EEA2,
+                SECURITY_ALGORITHM_HMAC_SHA1_96, false);
+
+        assertThat(update.getConnectionEvent()).isEqualTo(CONNECTION_EVENT_VOLTE_SIP);
+        assertThat(update.getEncryption()).isEqualTo(SECURITY_ALGORITHM_EEA2);
+        assertThat(update.getIntegrity()).isEqualTo(SECURITY_ALGORITHM_HMAC_SHA1_96);
+        assertThat(update.isUnprotectedEmergency()).isFalse();
+    }
+
+    @Test
+    public void testParcel() {
+        SecurityAlgorithmUpdate update = new SecurityAlgorithmUpdate(
+                CONNECTION_EVENT_VOLTE_SIP, SECURITY_ALGORITHM_EEA2,
+                SECURITY_ALGORITHM_HMAC_SHA1_96, false);
+
+        Parcel p = Parcel.obtain();
+        update.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        SecurityAlgorithmUpdate fromParcel = SecurityAlgorithmUpdate.CREATOR.createFromParcel(p);
+        assertThat(fromParcel).isEqualTo(update);
+    }
+
+    @Test
+    public void testConvertSecurityAlgorithmUpdate() {
+        android.hardware.radio.network.SecurityAlgorithmUpdate aidlUpdate =
+                new android.hardware.radio.network.SecurityAlgorithmUpdate();
+        aidlUpdate.connectionEvent = ConnectionEvent.PS_SIGNALLING_3G;
+        aidlUpdate.encryption = SecurityAlgorithm.UEA1;
+        aidlUpdate.integrity = SecurityAlgorithm.AUTH_HMAC_SHA2_256_128;
+        aidlUpdate.isUnprotectedEmergency = true;
+
+        assertThat(RILUtils.convertSecurityAlgorithmUpdate(aidlUpdate))
+                .isEqualTo(
+                        new SecurityAlgorithmUpdate(
+                                CONNECTION_EVENT_PS_SIGNALLING_3G,
+                                SECURITY_ALGORITHM_UEA1,
+                                SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+                                true));
+    }
+}
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 0358809..488ebbb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
@@ -26,8 +26,10 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -49,6 +51,7 @@
 import android.testing.TestableLooper;
 
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionDatabaseManager.SubscriptionDatabaseManagerCallback;
 
 import org.junit.After;
@@ -117,10 +120,27 @@
     static final int FAKE_TP_MESSAGE_REFERENCE2 = 456;
     static final int FAKE_USER_ID1 = 10;
     static final int FAKE_USER_ID2 = 11;
+    static final int FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED = 1;
+    static final int FAKE_SATELLITE_ATTACH_FOR_CARRIER_DISABLED = 0;
+    static final int FAKE_SATELLITE_IS_NTN_ENABLED = 1;
+    static final int FAKE_SATELLITE_IS_NTN_DISABLED = 0;
+    static final int FAKE_SERVICE_CAPABILITIES_1 =
+            SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK;
+    static final int FAKE_SERVICE_CAPABILITIES_2 =
+            SubscriptionManager.SERVICE_CAPABILITY_SMS_BITMASK;
+    static final int FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED = 1;
+    static final int FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED = 0;
+    static final String FAKE_SATELLITE_ENTITLEMENT_PLMNS1 = "123123,12310";
+    static final String FAKE_SATELLITE_ENTITLEMENT_PLMNS2 = "";
 
     static final String FAKE_MAC_ADDRESS1 = "DC:E5:5B:38:7D:40";
     static final String FAKE_MAC_ADDRESS2 = "DC:B5:4F:47:F3:4C";
 
+    private FeatureFlags mFeatureFlags;
+
+    static final int FAKE_TRANSFER_STATUS_TRANSFERRED_OUT = 1;
+    static final int FAKE_TRANSFER_STATUS_CONVERTED = 2;
+
     static final SubscriptionInfoInternal FAKE_SUBSCRIPTION_INFO1 =
             new SubscriptionInfoInternal.Builder()
                     .setId(1)
@@ -186,7 +206,14 @@
                     .setLastUsedTPMessageReference(FAKE_TP_MESSAGE_REFERENCE1)
                     .setUserId(FAKE_USER_ID1)
                     .setSatelliteEnabled(0)
+                    .setSatelliteAttachEnabledForCarrier(
+                            FAKE_SATELLITE_ATTACH_FOR_CARRIER_DISABLED)
+                    .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_DISABLED)
                     .setGroupDisabled(false)
+                    .setServiceCapabilities(FAKE_SERVICE_CAPABILITIES_1)
+                    .setTransferStatus(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT)
+                    .setSatelliteEntitlementStatus(FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED)
+                    .setSatelliteEntitlementPlmns(FAKE_SATELLITE_ENTITLEMENT_PLMNS2)
                     .build();
 
     static final SubscriptionInfoInternal FAKE_SUBSCRIPTION_INFO2 =
@@ -254,7 +281,14 @@
                     .setLastUsedTPMessageReference(FAKE_TP_MESSAGE_REFERENCE2)
                     .setUserId(FAKE_USER_ID2)
                     .setSatelliteEnabled(1)
+                    .setSatelliteAttachEnabledForCarrier(
+                            FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED)
+                    .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_ENABLED)
                     .setGroupDisabled(false)
+                    .setServiceCapabilities(FAKE_SERVICE_CAPABILITIES_2)
+                    .setTransferStatus(FAKE_TRANSFER_STATUS_CONVERTED)
+                    .setSatelliteEntitlementStatus(FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED)
+                    .setSatelliteEntitlementPlmns(FAKE_SATELLITE_ENTITLEMENT_PLMNS1)
                     .build();
 
     private SubscriptionDatabaseManager mDatabaseManagerUT;
@@ -406,14 +440,18 @@
             ((Runnable) invocation.getArguments()[0]).run();
             return null;
         }).when(mSubscriptionDatabaseManagerCallback).invokeFromExecutor(any(Runnable.class));
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
 
         ((MockContentResolver) mContext.getContentResolver()).addProvider(
                 Telephony.Carriers.CONTENT_URI.getAuthority(), mSubscriptionProvider);
 
         doReturn(1).when(mUiccController).convertToPublicCardId(eq(FAKE_ICCID1));
         doReturn(2).when(mUiccController).convertToPublicCardId(eq(FAKE_ICCID2));
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        when(mFeatureFlags.dataOnlyCellularService()).thenReturn(true);
+        when(mFeatureFlags.supportPsimToEsimConversion()).thenReturn(true);
         mDatabaseManagerUT = new SubscriptionDatabaseManager(mContext, Looper.myLooper(),
-                mSubscriptionDatabaseManagerCallback);
+                mFeatureFlags, mSubscriptionDatabaseManagerCallback);
         logd("SubscriptionDatabaseManagerTest -Setup!");
     }
 
@@ -518,7 +556,7 @@
         mContextFixture.putBooleanResource(com.android.internal.R.bool
                 .config_subscription_database_async_update, false);
         mDatabaseManagerUT = new SubscriptionDatabaseManager(mContext, Looper.myLooper(),
-                mSubscriptionDatabaseManagerCallback);
+                mFeatureFlags, mSubscriptionDatabaseManagerCallback);
 
         assertThat(insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1).getSubscriptionId())
                 .isEqualTo(1);
@@ -1930,6 +1968,145 @@
     }
 
     @Test
+    public void testUpdateCarrierHandoverToSatelliteEnabled() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setSatelliteAttachEnabledForCarrier(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setSatelliteAttachEnabledForCarrier(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setSatelliteAttachEnabledForCarrier(
+                        FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER))
+                .isEqualTo(FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
+                FAKE_SATELLITE_ATTACH_FOR_CARRIER_DISABLED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId())
+                .getSatelliteAttachEnabledForCarrier())
+                .isEqualTo(FAKE_SATELLITE_ATTACH_FOR_CARRIER_DISABLED);
+    }
+
+    @Test
+    public void testUpdateSatelliteNtn() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setNtn(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_SATELLITE_IS_NTN_ENABLED));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setNtn(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_SATELLITE_IS_NTN_ENABLED);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_ENABLED)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_IS_NTN)).isEqualTo(FAKE_SATELLITE_IS_NTN_ENABLED);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_IS_NTN, FAKE_SATELLITE_IS_NTN_DISABLED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getOnlyNonTerrestrialNetwork())
+                .isEqualTo(FAKE_SATELLITE_IS_NTN_DISABLED);
+    }
+
+    @Test
+    public void testSetGroupDisabled() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setGroupDisabled(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(), true));
+
+        insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setGroupDisabled(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(), true);
+        processAllMessages();
+
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+            FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).isGroupDisabled()).isTrue();
+
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+        Mockito.clearInvocations(mSubscriptionDatabaseManagerCallback);
+
+        mDatabaseManagerUT.setGroupDisabled(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(), true);
+        processAllMessages();
+        verify(mSubscriptionDatabaseManagerCallback, never()).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+            FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).isGroupDisabled()).isTrue();
+
+        mDatabaseManagerUT.setGroupDisabled(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(), false);
+        processAllMessages();
+        verify(mSubscriptionDatabaseManagerCallback, times(1)).onSubscriptionChanged(eq(1));
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).isGroupDisabled()).isFalse();
+    }
+
+    @Test
+    public void testUpdateSatelliteNtnWithFeatureDisabled() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setSatelliteAttachEnabledForCarrier(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setSatelliteAttachEnabledForCarrier(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_SATELLITE_IS_NTN_DISABLED);
+        processAllMessages();
+
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+        reset(mSubscriptionDatabaseManagerCallback);
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_ENABLED)
+                .build();
+
+        int subId = subInfo.getSubscriptionId();
+        // Verify the cache value is not same as the inserted one.
+        assertWithMessage("Subscription info cache value is not different.")
+                .that(mDatabaseManagerUT.getSubscriptionInfoInternal(subId)).isNotEqualTo(subInfo);
+
+        // Load subscription info from the database.
+        mDatabaseManagerUT.reloadDatabaseSync();
+        processAllMessages();
+
+        // Verify the database value is not same as the inserted one.
+        assertWithMessage("Subscription info database value is not different.")
+                .that(mDatabaseManagerUT.getSubscriptionInfoInternal(subId)).isNotEqualTo(subInfo);
+
+        verify(mSubscriptionDatabaseManagerCallback, never()).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_IS_NTN)).isNotEqualTo(FAKE_SATELLITE_IS_NTN_ENABLED);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_IS_NTN, FAKE_SATELLITE_IS_NTN_ENABLED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getOnlyNonTerrestrialNetwork())
+                .isNotEqualTo(FAKE_SATELLITE_IS_NTN_ENABLED);
+    }
+
+    @Test
     public void testUpdateSubscriptionsInGroup() throws Exception {
         insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
         insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO2);
@@ -2033,7 +2210,8 @@
                     }
                 };
         assertThat(callback.getExecutor()).isEqualTo(executor);
-        mDatabaseManagerUT = new SubscriptionDatabaseManager(mContext, Looper.myLooper(), callback);
+        mDatabaseManagerUT = new SubscriptionDatabaseManager(mContext, Looper.myLooper(),
+                mFeatureFlags, callback);
         processAllMessages();
 
         assertThat(latch.getCount()).isEqualTo(1);
@@ -2047,4 +2225,133 @@
         processAllMessages();
         assertThat(latch.getCount()).isEqualTo(0);
     }
+
+    @Test
+    public void testUpdateServiceCapabilities() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setServiceCapabilities(1,
+                        FAKE_SERVICE_CAPABILITIES_2));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setServiceCapabilities(subInfo.getSubscriptionId(),
+                FAKE_SERVICE_CAPABILITIES_2);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo).setServiceCapabilities(
+                FAKE_SERVICE_CAPABILITIES_2).build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(1,
+                SimInfo.COLUMN_SERVICE_CAPABILITIES))
+                .isEqualTo(FAKE_SERVICE_CAPABILITIES_2);
+        mDatabaseManagerUT.setSubscriptionProperty(
+                1, SimInfo.COLUMN_SERVICE_CAPABILITIES,
+                FAKE_SERVICE_CAPABILITIES_1);
+        assertThat(
+                mDatabaseManagerUT.getSubscriptionInfoInternal(1).getServiceCapabilities())
+                .isEqualTo(FAKE_SERVICE_CAPABILITIES_1);
+    }
+
+    @Test
+    public void testSetTransferStatus() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setTransferStatus(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_TRANSFER_STATUS_TRANSFERRED_OUT));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setTransferStatus(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_TRANSFER_STATUS_TRANSFERRED_OUT);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setTransferStatus(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(1)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_TRANSFER_STATUS)).isEqualTo(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_TRANSFER_STATUS, FAKE_TRANSFER_STATUS_CONVERTED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getTransferStatus())
+                .isEqualTo(FAKE_TRANSFER_STATUS_CONVERTED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getTransferStatus())
+                .isNotEqualTo(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT);
+    }
+
+    @Test
+    public void testUpdateSatelliteEntitlementStatus() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setSatelliteEntitlementStatus(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setSatelliteEntitlementStatus(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setSatelliteEntitlementStatus(FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS))
+                .isEqualTo(FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+                FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId())
+                .getSatelliteEntitlementStatus())
+                .isEqualTo(FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED);
+    }
+
+    @Test
+    public void testUpdateSatelliteEntitlementPlmns() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setSatelliteEntitlementPlmns(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_SATELLITE_ENTITLEMENT_PLMNS1));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setSatelliteEntitlementPlmns(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_SATELLITE_ENTITLEMENT_PLMNS1);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setSatelliteEntitlementPlmns(FAKE_SATELLITE_ENTITLEMENT_PLMNS1)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS))
+                .isEqualTo(FAKE_SATELLITE_ENTITLEMENT_PLMNS1);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+                FAKE_SATELLITE_ENTITLEMENT_PLMNS2);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId())
+                .getSatelliteEntitlementPlmns())
+                .isEqualTo(FAKE_SATELLITE_ENTITLEMENT_PLMNS2);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
index e03256b..6e2c5bf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
@@ -18,16 +18,21 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.ParcelUuid;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.UiccAccessRule;
 import android.telephony.ims.ImsMmTelManager;
 
+import com.android.internal.telephony.flags.Flags;
+
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 
 public class SubscriptionInfoInternalTest {
     private final SubscriptionInfoInternal mSubInfo =
@@ -101,7 +106,22 @@
                             .FAKE_TP_MESSAGE_REFERENCE1)
                     .setUserId(SubscriptionDatabaseManagerTest.FAKE_USER_ID1)
                     .setSatelliteEnabled(1)
+                    .setSatelliteAttachEnabledForCarrier(
+                            SubscriptionDatabaseManagerTest
+                                    .FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED)
+                    .setOnlyNonTerrestrialNetwork(
+                            SubscriptionDatabaseManagerTest.FAKE_SATELLITE_IS_NTN_ENABLED)
                     .setGroupDisabled(false)
+                    .setOnlyNonTerrestrialNetwork(1)
+                    .setServiceCapabilities(
+                            SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK)
+                    .setTransferStatus(1)
+                    .setSatelliteEntitlementStatus(
+                            SubscriptionDatabaseManagerTest
+                                    .FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED)
+                    .setSatelliteEntitlementPlmns(
+                            SubscriptionDatabaseManagerTest
+                                    .FAKE_SATELLITE_ENTITLEMENT_PLMNS1)
                     .build();
 
     private final SubscriptionInfoInternal mSubInfoNull =
@@ -128,10 +148,15 @@
                     .setRcsConfig(new byte[0])
                     .setAllowedNetworkTypesForReasons("")
                     .setDeviceToDeviceStatusSharingContacts("")
+                    .setTransferStatus(1)
                     .build();
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testSubscriptionInfoInternalSetAndGet() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE);
         assertThat(mSubInfo.getSubscriptionId()).isEqualTo(1);
         assertThat(mSubInfo.getIccId()).isEqualTo(SubscriptionDatabaseManagerTest.FAKE_ICCID1);
         assertThat(mSubInfo.getSimSlotIndex()).isEqualTo(0);
@@ -211,7 +236,22 @@
                 SubscriptionDatabaseManagerTest.FAKE_TP_MESSAGE_REFERENCE1);
         assertThat(mSubInfo.getUserId()).isEqualTo(SubscriptionDatabaseManagerTest.FAKE_USER_ID1);
         assertThat(mSubInfo.getSatelliteEnabled()).isEqualTo(1);
+        assertThat(mSubInfo.getSatelliteAttachEnabledForCarrier())
+                .isEqualTo(SubscriptionDatabaseManagerTest
+                        .FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED);
+        assertThat(mSubInfo.getOnlyNonTerrestrialNetwork()).isEqualTo(
+                SubscriptionDatabaseManagerTest.FAKE_SATELLITE_IS_NTN_ENABLED);
         assertThat(mSubInfo.isGroupDisabled()).isFalse();
+        assertThat(mSubInfo.getOnlyNonTerrestrialNetwork()).isEqualTo(1);
+        assertThat(mSubInfo.getServiceCapabilities()).isEqualTo(
+                SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK);
+        assertThat(mSubInfo.getTransferStatus()).isEqualTo(1);
+        assertThat(mSubInfo.getSatelliteEntitlementStatus())
+                .isEqualTo(SubscriptionDatabaseManagerTest
+                        .FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED);
+        assertThat(mSubInfo.getSatelliteEntitlementPlmns())
+                .isEqualTo(SubscriptionDatabaseManagerTest
+                        .FAKE_SATELLITE_ENTITLEMENT_PLMNS1);
     }
 
     @Test
@@ -223,6 +263,7 @@
 
     @Test
     public void testConvertToSubscriptionInfo() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE);
         SubscriptionInfo subInfo = mSubInfo.toSubscriptionInfo();
 
         assertThat(subInfo.getSubscriptionId()).isEqualTo(1);
@@ -274,6 +315,10 @@
         assertThat(subInfo.getPortIndex()).isEqualTo(
                 SubscriptionManager.USAGE_SETTING_DEFAULT);
         assertThat(subInfo.isGroupDisabled()).isFalse();
+        assertThat(subInfo.isOnlyNonTerrestrialNetwork()).isTrue();
+        assertThat(subInfo.getServiceCapabilities()).isEqualTo(
+                Set.of(SubscriptionManager.SERVICE_CAPABILITY_DATA));
+        assertThat(mSubInfo.getTransferStatus()).isEqualTo(1);
     }
 
     @Test
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 ddc9171..9eca6f6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
@@ -97,6 +97,7 @@
 import android.util.ArraySet;
 import android.util.Base64;
 
+import com.android.internal.R;
 import com.android.internal.telephony.ContextFixture;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
@@ -104,12 +105,16 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.euicc.EuiccController;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.subscription.SubscriptionDatabaseManager.SubscriptionDatabaseManagerCallback;
 import com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.SubscriptionProvider;
+import com.android.internal.telephony.subscription.SubscriptionManagerService.BinderWrapper;
 import com.android.internal.telephony.subscription.SubscriptionManagerService.SubscriptionManagerServiceCallback;
 import com.android.internal.telephony.subscription.SubscriptionManagerService.SubscriptionMap;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.UiccSlot;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
 import org.junit.After;
@@ -153,7 +158,8 @@
     // mocked
     private SubscriptionManagerServiceCallback mMockedSubscriptionManagerServiceCallback;
     private EuiccController mEuiccController;
-
+    private FeatureFlags mFlags;
+    private BinderWrapper mBinder;
     private Set<Integer> mActiveSubs = new ArraySet<>();
 
     @Rule
@@ -194,12 +200,18 @@
         doReturn(FAKE_ICCID1).when(mUiccController).convertToCardString(eq(1));
         doReturn(FAKE_ICCID2).when(mUiccController).convertToCardString(eq(2));
 
+        mBinder = Mockito.mock(BinderWrapper.class);
+        doReturn(FAKE_USER_HANDLE).when(mBinder).getCallingUserHandle();
+        replaceInstance(SubscriptionManagerService.class, "BINDER_WRAPPER", null, mBinder);
+
         doReturn(new int[0]).when(mSubscriptionManager).getCompleteActiveSubscriptionIdList();
 
         ((MockContentResolver) mContext.getContentResolver()).addProvider(
                 Telephony.Carriers.CONTENT_URI.getAuthority(), mSubscriptionProvider);
 
-        mSubscriptionManagerServiceUT = new SubscriptionManagerService(mContext, Looper.myLooper());
+        mFlags = Mockito.mock(FeatureFlags.class);
+        mSubscriptionManagerServiceUT = new SubscriptionManagerService(mContext, Looper.myLooper(),
+                mFlags);
 
         monitorTestableLooper(new TestableLooper(getBackgroundHandler().getLooper()));
         monitorTestableLooper(new TestableLooper(getSubscriptionDatabaseManager().getLooper()));
@@ -222,6 +234,11 @@
         doReturn(true).when(mUserManager)
                 .isManagedProfile(eq(FAKE_MANAGED_PROFILE_USER_HANDLE.getIdentifier()));
 
+        // Due to affect exist implementation, bypass feature flag.
+        doReturn(false).when(mFlags).enforceTelephonyFeatureMappingForPublicApis();
+        doReturn(true).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         logd("SubscriptionManagerServiceTest -Setup!");
     }
 
@@ -253,6 +270,12 @@
         return (SubscriptionDatabaseManager) field.get(mSubscriptionManagerServiceUT);
     }
 
+    private SubscriptionDatabaseManagerCallback getSubscriptionDatabaseCallback() throws Exception {
+        Field field = SubscriptionDatabaseManager.class.getDeclaredField("mCallback");
+        field.setAccessible(true);
+        return (SubscriptionDatabaseManagerCallback) field.get(getSubscriptionDatabaseManager());
+    }
+
     /**
      * Insert the subscription info to the database. This is an instant insertion method. For real
      * insertion sequence please use {@link #testInsertNewSim()}.
@@ -262,7 +285,6 @@
      */
     private int insertSubscription(@NonNull SubscriptionInfoInternal subInfo) {
         try {
-            mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
             subInfo = new SubscriptionInfoInternal.Builder(subInfo)
                     .setId(SubscriptionManager.INVALID_SUBSCRIPTION_ID).build();
             int subId = getSubscriptionDatabaseManager().insertSubscriptionInfo(subInfo);
@@ -274,17 +296,12 @@
             field.setAccessible(true);
             SubscriptionMap<Integer, Integer> map = (SubscriptionMap<Integer, Integer>)
                     field.get(mSubscriptionManagerServiceUT);
-            Class[] cArgs = new Class[2];
-            cArgs[0] = Object.class;
-            cArgs[1] = Object.class;
 
             if (subInfo.getSimSlotIndex() >= 0) {
                 // Change the slot -> subId mapping
                 map.put(subInfo.getSimSlotIndex(), subId);
             }
 
-            mContextFixture.removeCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
-            processAllMessages();
             verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(subId));
             Mockito.clearInvocations(mMockedSubscriptionManagerServiceCallback);
 
@@ -336,6 +353,11 @@
         assertThat(subInfo.getSimSlotIndex()).isEqualTo(0);
         assertThat(subInfo.getSubscriptionType()).isEqualTo(
                 SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+
+        // Invalid slot index should trigger IllegalArgumentException
+        assertThrows(IllegalArgumentException.class,
+                () -> mSubscriptionManagerServiceUT.addSubInfo(FAKE_ICCID1, FAKE_CARRIER_NAME1,
+                        2, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM));
     }
 
     @Test
@@ -397,7 +419,29 @@
     }
 
     @Test
+    public void testSetAdminOwned() {
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+        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);
+        assertThat(subInfo).isNotNull();
+        assertThat(subInfo.getGroupOwner()).isEqualTo(groupOwner);
+        verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(1));
+    }
+
+    @Test
+    @DisableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
     public void testSetPhoneNumber() {
+        doReturn(false).when(mFlags).enforceTelephonyFeatureMapping();
+        doReturn(true).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
         mSubscriptionManagerServiceUT.addSubInfo(FAKE_ICCID1, FAKE_CARRIER_NAME1,
                 0, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
@@ -434,6 +478,41 @@
     }
 
     @Test
+    @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
+    public void testSetPhoneNumber_EnabledEnforceTelephonyFeatureMappingForPublicApis() {
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+        mSubscriptionManagerServiceUT.addSubInfo(FAKE_ICCID1, FAKE_CARRIER_NAME1,
+                0, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+        processAllMessages();
+
+        verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(1));
+        Mockito.clearInvocations(mMockedSubscriptionManagerServiceCallback);
+
+        // Grant carrier privilege
+        setCarrierPrivilegesForSubId(true, 1);
+
+        // Enabled FeatureFlags and ENABLE_FEATURE_MAPPING, telephony features are defined
+        doReturn(true).when(mFlags).enforceTelephonyFeatureMappingForPublicApis();
+        doReturn(true).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+        try {
+            mSubscriptionManagerServiceUT.setPhoneNumber(1,
+                    SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, FAKE_PHONE_NUMBER2,
+                    CALLING_PACKAGE, CALLING_FEATURE);
+        } catch (UnsupportedOperationException e) {
+            fail("Not expect exception " + e.getMessage());
+        }
+
+        // Telephony features is not defined, expect UnsupportedOperationException.
+        doReturn(false).when(mPackageManager).hasSystemFeature(
+                eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+        assertThrows(UnsupportedOperationException.class,
+                () -> mSubscriptionManagerServiceUT.setPhoneNumber(1,
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, FAKE_PHONE_NUMBER2,
+                        CALLING_PACKAGE, CALLING_FEATURE));
+    }
+
+    @Test
     public void testGetAllSubInfoList() {
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
@@ -471,6 +550,10 @@
 
         // Grant READ_PHONE_STATE permission
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        // Allow the application to perform.
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(),
+                        nullable(String.class), nullable(String.class), nullable(String.class));
         // Grant identifier access
         setIdentifierAccess(true);
         // Revoke carrier privileges.
@@ -528,6 +611,11 @@
 
         // Grant READ_PHONE_STATE permission
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        // Allow the application to perform.
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(),
+                        nullable(String.class), nullable(String.class), nullable(String.class));
+
         setIdentifierAccess(false);
         setCarrierPrivilegesForSubId(false, 1);
         setCarrierPrivilegesForSubId(false, 2);
@@ -648,6 +736,7 @@
 
         mSubscriptionManagerServiceUT.setDefaultDataSubId(1);
         assertThat(mSubscriptionManagerServiceUT.getDefaultDataSubId()).isEqualTo(1);
+        verify(mProxyController).setRadioCapability(any());
 
         assertThat(Settings.Global.getInt(mContext.getContentResolver(),
                         Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION)).isEqualTo(1);
@@ -675,6 +764,26 @@
     }
 
     @Test
+    public void testSingleSimSetDefaultDataSubId() {
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+        doReturn(1).when(mProxyController).getMinRafSupported();
+        doReturn(2).when(mProxyController).getMaxRafSupported();
+        insertSubscription(FAKE_SUBSCRIPTION_INFO2);
+
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+
+        mSubscriptionManagerServiceUT.setDefaultDataSubId(1);
+        assertThat(mSubscriptionManagerServiceUT.getDefaultDataSubId()).isEqualTo(1);
+        ArgumentCaptor<RadioAccessFamily[]> rafsCaptor = ArgumentCaptor.forClass(
+                RadioAccessFamily[].class);
+        verify(mProxyController).setRadioCapability(rafsCaptor.capture());
+        RadioAccessFamily[] rafs = (RadioAccessFamily[]) rafsCaptor.getValue();
+        assertThat(rafs[0].getRadioAccessFamily()).isEqualTo(1);
+        assertThat(rafs[1].getRadioAccessFamily()).isEqualTo(2);
+    }
+
+    @Test
     public void testSetDefaultSmsSubId() throws Exception {
         clearInvocations(mContext);
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
@@ -735,13 +844,17 @@
 
         // Should get an empty list without READ_PHONE_STATE.
         assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
-                CALLING_PACKAGE, CALLING_FEATURE)).isEmpty();
+                CALLING_PACKAGE, CALLING_FEATURE, true)).isEmpty();
 
         // Grant READ_PHONE_STATE permission for insertion.
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        // Allow the application to perform.
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(),
+                        nullable(String.class), nullable(String.class), nullable(String.class));
 
         List<SubscriptionInfo> subInfos = mSubscriptionManagerServiceUT
-                .getActiveSubscriptionInfoList(CALLING_PACKAGE, CALLING_FEATURE);
+                .getActiveSubscriptionInfoList(CALLING_PACKAGE, CALLING_FEATURE, true);
         assertThat(subInfos).hasSize(1);
         assertThat(subInfos.get(0).getIccId()).isEmpty();
         assertThat(subInfos.get(0).getCardString()).isEmpty();
@@ -752,7 +865,7 @@
         setCarrierPrivilegesForSubId(true, 1);
 
         subInfos = mSubscriptionManagerServiceUT
-                .getActiveSubscriptionInfoList(CALLING_PACKAGE, CALLING_FEATURE);
+                .getActiveSubscriptionInfoList(CALLING_PACKAGE, CALLING_FEATURE, true);
         assertThat(subInfos).hasSize(1);
         assertThat(subInfos.get(0)).isEqualTo(FAKE_SUBSCRIPTION_INFO1.toSubscriptionInfo());
     }
@@ -768,6 +881,11 @@
 
         // Grant READ_PHONE_STATE permission for insertion.
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        // Allow the application to perform.
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(),
+                        nullable(String.class), nullable(String.class), nullable(String.class));
+
         SubscriptionInfo subInfo = mSubscriptionManagerServiceUT
                 .getActiveSubscriptionInfoForSimSlotIndex(0, CALLING_PACKAGE,
                         CALLING_FEATURE);
@@ -862,6 +980,10 @@
     public void testUpdateEmbeddedSubscriptionsNullResult() {
         // Grant READ_PHONE_STATE permission.
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        // Allow the application to perform.
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(),
+                        nullable(String.class), nullable(String.class), nullable(String.class));
 
         doReturn(null).when(mEuiccController).blockingGetEuiccProfileInfoList(anyInt());
 
@@ -883,6 +1005,11 @@
 
         // Grant READ_PHONE_STATE permission for insertion.
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        // Allow the application to perform.
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(),
+                        nullable(String.class), nullable(String.class), nullable(String.class));
+
         SubscriptionInfo subInfo = mSubscriptionManagerServiceUT
                 .getActiveSubscriptionInfoForSimSlotIndex(0, CALLING_PACKAGE,
                         CALLING_FEATURE);
@@ -932,15 +1059,15 @@
 
         // Should fail without READ_PHONE_STATE
         assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT
-                .getActiveSubInfoCount(CALLING_PACKAGE, CALLING_FEATURE));
+                .getActiveSubInfoCount(CALLING_PACKAGE, CALLING_FEATURE, true));
 
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         assertThat(mSubscriptionManagerServiceUT.getActiveSubInfoCount(
-                CALLING_PACKAGE, CALLING_FEATURE)).isEqualTo(2);
+                CALLING_PACKAGE, CALLING_FEATURE, true)).isEqualTo(2);
     }
 
     @Test
-    public void testSetIconTint() throws Exception {
+    public void testSetIconTint() {
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
 
         // Should fail without MODIFY_PHONE_STATE
@@ -1089,8 +1216,6 @@
 
     @Test
     public void testIsSubscriptionAssociatedWithUser() {
-        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
-
         // Should fail without MANAGE_SUBSCRIPTION_USER_ASSOCIATION
         assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT
                 .isSubscriptionAssociatedWithUser(1, FAKE_USER_HANDLE));
@@ -1101,6 +1226,12 @@
                 Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
+        // Should fail for non-existent sub Id
+        assertThrows(IllegalArgumentException.class, () -> mSubscriptionManagerServiceUT
+                .isSubscriptionAssociatedWithUser(1, FAKE_USER_HANDLE));
+
+        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+
         mSubscriptionManagerServiceUT.setSubscriptionUserHandle(FAKE_USER_HANDLE, 1);
         processAllMessages();
         verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(1));
@@ -1122,6 +1253,339 @@
     }
 
     @Test
+    @EnableCompatChanges({SubscriptionManagerService.REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID,
+            SubscriptionManagerService.FILTER_ACCESSIBLE_SUBS_BY_USER})
+    public void testIsSubscriptionAssociatedWithUserMultiSubs() {
+        doReturn(true).when(mFlags).workProfileApiSplit();
+        doReturn(true).when(mFlags).enforceSubscriptionUserFilter();
+        mContextFixture.addCallingOrSelfPermission(
+                Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
+        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+        mSubscriptionManagerServiceUT.setSubscriptionUserHandle(
+                UserHandle.of(UserHandle.USER_NULL), 1);
+
+        // Verify sub 1 unassociated is visible to all profiles
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1,
+                FAKE_USER_HANDLE)).isEqualTo(true);
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1,
+                FAKE_MANAGED_PROFILE_USER_HANDLE)).isEqualTo(true);
+
+        // Assign sub 2 to work profile
+        insertSubscription(FAKE_SUBSCRIPTION_INFO2);
+        mSubscriptionManagerServiceUT.setSubscriptionUserHandle(
+                FAKE_MANAGED_PROFILE_USER_HANDLE, 2);
+        processAllMessages();
+        // Verify work profile can only see its dedicated sub 2
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(2,
+                FAKE_MANAGED_PROFILE_USER_HANDLE)).isEqualTo(true);
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1,
+                FAKE_MANAGED_PROFILE_USER_HANDLE)).isEqualTo(false);
+        // Verify personal profile can only see the unassigned sub 1
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1,
+                FAKE_USER_HANDLE)).isEqualTo(true);
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(2,
+                FAKE_USER_HANDLE)).isEqualTo(false);
+
+        // Sub 2 is deactivated, but still on device, verify visibility doesn't change.
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        mSubscriptionManagerServiceUT.updateSimState(
+                1, TelephonyManager.SIM_STATE_NOT_READY, null, null);
+        processAllMessages();
+        // Verify work profile can only see its dedicated sub 2
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(2,
+                FAKE_MANAGED_PROFILE_USER_HANDLE)).isEqualTo(true);
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1,
+                FAKE_MANAGED_PROFILE_USER_HANDLE)).isEqualTo(false);
+        // Verify personal profile can only see the unassigned sub 1
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1,
+                FAKE_USER_HANDLE)).isEqualTo(true);
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(2,
+                FAKE_USER_HANDLE)).isEqualTo(false);
+
+        // Sub 2 is removed from the device.
+        mSubscriptionManagerServiceUT.updateSimState(
+                1, TelephonyManager.SIM_STATE_ABSENT, null, null);
+        processAllMessages();
+        // Verify the visibility of the unassigned sub 1 is restored to both profiles.
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1,
+                FAKE_USER_HANDLE)).isEqualTo(true);
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionAssociatedWithUser(1,
+                FAKE_MANAGED_PROFILE_USER_HANDLE)).isEqualTo(true);
+    }
+
+    @Test
+    @EnableCompatChanges({SubscriptionManagerService.REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID,
+            SubscriptionManagerService.FILTER_ACCESSIBLE_SUBS_BY_USER})
+    public void testSubscriptionAssociationWorkProfileCallerVisibility() {
+        // Split mode is defined as when a profile owns a dedicated sub, it loses the visibility to
+        // the unassociated sub.
+        doReturn(true).when(mFlags).enforceSubscriptionUserFilter();
+        doReturn(true).when(mFlags).workProfileApiSplit();
+        mContextFixture.addCallingOrSelfPermission(
+                Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
+        // Sub 1 is associated with work profile; Sub 2 is unassociated.
+        int subId1 = insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+        mSubscriptionManagerServiceUT.setSubscriptionUserHandle(
+                FAKE_MANAGED_PROFILE_USER_HANDLE, subId1);
+        int subId2 = insertSubscription(FAKE_SUBSCRIPTION_INFO2);
+        mSubscriptionManagerServiceUT.setSubscriptionUserHandle(
+                UserHandle.of(UserHandle.USER_NULL), subId2);
+        // Set Sub 1 default data sub
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        mSubscriptionManagerServiceUT.setDefaultDataSubId(subId1);
+        processAllMessages();
+
+        // Calling from work profile
+        doReturn(FAKE_MANAGED_PROFILE_USER_HANDLE).when(mBinder).getCallingUserHandle();
+
+        // Test getAccessibleSubscriptionInfoList
+        doReturn(true).when(mEuiccManager).isEnabled();
+        doReturn(true).when(mSubscriptionManager).canManageSubscription(
+                any(SubscriptionInfo.class), eq(CALLING_PACKAGE));
+        assertThat(mSubscriptionManagerServiceUT.getAccessibleSubscriptionInfoList(
+                CALLING_PACKAGE)).isEqualTo(List.of(FAKE_SUBSCRIPTION_INFO1.toSubscriptionInfo()));
+        // Test getActiveSubIdList, System
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false/*visible only*/))
+                .isEqualTo(new int[]{subId1, subId2});
+        // Test get getActiveSubInfoCount - forAllProfiles: false
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubInfoCount(
+                CALLING_PACKAGE, CALLING_FEATURE, false)).isEqualTo(1);
+        // Test get getActiveSubInfoCount - forAllProfiles: true
+        assertThrows(SecurityException.class,
+                () -> mSubscriptionManagerServiceUT.getActiveSubInfoCount(
+                        CALLING_PACKAGE, CALLING_FEATURE, true));
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubInfoCount(
+                CALLING_PACKAGE, CALLING_FEATURE, true)).isEqualTo(2);
+        mContextFixture.removeCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        // Test getActiveSubscriptionInfo
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfo(
+                subId1, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId()).isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfo(
+                subId2, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId()).isEqualTo(subId2);
+        // Test getActiveSubscriptionInfoForIccId
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForIccId(
+                FAKE_ICCID1, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
+                .isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForIccId(
+                FAKE_ICCID2, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
+                .isEqualTo(subId2);
+        // Test getActiveSubscriptionInfoForSimSlotIndex
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForSimSlotIndex(
+                0, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
+                .isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForSimSlotIndex(
+                1, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
+                .isEqualTo(subId2);
+        // Test getActiveSubscriptionInfoList - forAllProfiles: false
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
+                CALLING_PACKAGE, CALLING_FEATURE, false)
+                .stream().map(SubscriptionInfo::getSubscriptionId)
+                .toList()).isEqualTo(List.of(subId1));
+        // Test getActiveSubscriptionInfoList - forAllProfiles: true
+        assertThrows(SecurityException.class,
+                () -> mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
+                        CALLING_PACKAGE, CALLING_FEATURE, true));
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
+                        CALLING_PACKAGE, CALLING_FEATURE, true)
+                .stream().map(SubscriptionInfo::getSubscriptionId)
+                .toList()).isEqualTo(List.of(subId1, subId2));
+        mContextFixture.removeCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        // Test getAllSubInfoList
+        assertThat(mSubscriptionManagerServiceUT.getAllSubInfoList(CALLING_PACKAGE,
+                CALLING_FEATURE).stream().map(SubscriptionInfo::getSubscriptionId).toList())
+                .isEqualTo(List.of(subId1));
+        // Test getAvailableSubscriptionInfoList
+        assertThat(mSubscriptionManagerServiceUT.getAvailableSubscriptionInfoList(CALLING_PACKAGE,
+                CALLING_FEATURE).stream().map(SubscriptionInfo::getSubscriptionId).toList())
+                .isEqualTo(List.of(subId1, subId2));
+        // Test getDefaultDataSubId
+        assertThat(mSubscriptionManagerServiceUT.getDefaultDataSubId()).isEqualTo(subId1);
+        // Test getDefault<Sms/Voice>SubIdAsUser
+        assertThat(mSubscriptionManagerServiceUT.getDefaultSmsSubIdAsUser(
+                FAKE_MANAGED_PROFILE_USER_HANDLE.getIdentifier())).isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getDefaultSubIdAsUser(
+                FAKE_MANAGED_PROFILE_USER_HANDLE.getIdentifier())).isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getDefaultVoiceSubIdAsUser(
+                FAKE_MANAGED_PROFILE_USER_HANDLE.getIdentifier())).isEqualTo(subId1);
+        // Test getEnabledSubscriptionId
+        assertThat(mSubscriptionManagerServiceUT.getEnabledSubscriptionId(0)).isEqualTo(
+                subId1);
+        assertThat(mSubscriptionManagerServiceUT.getEnabledSubscriptionId(1)).isEqualTo(
+                subId2);
+        // Test getOpportunisticSubscriptions
+        mSubscriptionManagerServiceUT.setOpportunistic(true, subId1, CALLING_PACKAGE);
+        mSubscriptionManagerServiceUT.setOpportunistic(true, subId2, CALLING_PACKAGE);
+        processAllMessages();
+        assertThat(mSubscriptionManagerServiceUT.getOpportunisticSubscriptions(CALLING_PACKAGE,
+                CALLING_FEATURE).stream().map(SubscriptionInfo::getSubscriptionId).toList())
+                .isEqualTo(List.of(subId1, subId2));
+        // Test getSubscriptionInfo - can get both as it's an internal getter
+        assertThat(mSubscriptionManagerServiceUT.getSubscriptionInfo(subId1).getSubscriptionId())
+                .isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getSubscriptionInfo(subId2).getSubscriptionId())
+                .isEqualTo(subId2);
+        // Test getSubscriptionInfoListAssociatedWithUser
+        mContextFixture.addCallingOrSelfPermission(
+                Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
+        assertThat(mSubscriptionManagerServiceUT.getSubscriptionInfoListAssociatedWithUser(
+                FAKE_MANAGED_PROFILE_USER_HANDLE).stream().map(SubscriptionInfo::getSubscriptionId)
+                .toList()).isEqualTo(List.of(subId1));
+        // Test getSubscriptionsInGroup
+        setCarrierPrivilegesForSubId(true, subId1);
+        assertThat(mSubscriptionManagerServiceUT.getSubscriptionsInGroup(
+                ParcelUuid.fromString(FAKE_UUID1), CALLING_PACKAGE, CALLING_FEATURE)
+                .stream().map(SubscriptionInfo::getSubscriptionId).toList())
+                .isEqualTo(List.of(subId1));
+        // Test isActiveSubId
+        assertThat(mSubscriptionManagerServiceUT.isActiveSubId(subId1, CALLING_PACKAGE,
+                CALLING_FEATURE)).isTrue();
+        assertThat(mSubscriptionManagerServiceUT.isActiveSubId(subId2, CALLING_PACKAGE,
+                CALLING_FEATURE)).isTrue();
+        // Test isSubscriptionEnabled
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionEnabled(subId1)).isTrue();
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionEnabled(subId2)).isTrue();
+    }
+
+    @Test
+    @EnableCompatChanges({SubscriptionManagerService.REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID,
+            SubscriptionManagerService.FILTER_ACCESSIBLE_SUBS_BY_USER})
+    public void testSubscriptionAssociationPersonalCallerVisibility() {
+        // Split mode is defined as when a profile owns a dedicated sub, it loses the visibility to
+        // the unassociated sub.
+        doReturn(true).when(mFlags).enforceSubscriptionUserFilter();
+        doReturn(true).when(mFlags).workProfileApiSplit();
+        mContextFixture.addCallingOrSelfPermission(
+                Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
+        // Sub 1 is unassociated; Sub 2 is associated with work profile.
+        int subId1 = insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+        mSubscriptionManagerServiceUT.setSubscriptionUserHandle(
+                UserHandle.of(UserHandle.USER_NULL), subId1);
+        int subId2 = insertSubscription(FAKE_SUBSCRIPTION_INFO2);
+        mSubscriptionManagerServiceUT.setSubscriptionUserHandle(
+                FAKE_MANAGED_PROFILE_USER_HANDLE, subId2);
+        // Set Sub 1 default data sub
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        mSubscriptionManagerServiceUT.setDefaultDataSubId(subId1);
+        processAllMessages();
+
+        // Calling from a profile that owns no dedicated subs.
+        doReturn(FAKE_USER_HANDLE).when(mBinder).getCallingUserHandle();
+
+        // Test getAccessibleSubscriptionInfoList
+        doReturn(true).when(mEuiccManager).isEnabled();
+        doReturn(true).when(mSubscriptionManager).canManageSubscription(
+                any(SubscriptionInfo.class), eq(CALLING_PACKAGE));
+        assertThat(mSubscriptionManagerServiceUT.getAccessibleSubscriptionInfoList(
+                CALLING_PACKAGE)).isEqualTo(List.of(FAKE_SUBSCRIPTION_INFO1.toSubscriptionInfo()));
+        // Test getActiveSubIdList, System
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false/*visible only*/))
+                .isEqualTo(new int[]{subId1, subId2});
+        // Test get getActiveSubInfoCount- forAllProfiles: false
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubInfoCount(
+                CALLING_PACKAGE, CALLING_FEATURE, false)).isEqualTo(1);
+        // Test get getActiveSubInfoCount - forAllProfiles: true
+        assertThrows(SecurityException.class,
+                () -> mSubscriptionManagerServiceUT.getActiveSubInfoCount(
+                        CALLING_PACKAGE, CALLING_FEATURE, true));
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubInfoCount(
+                CALLING_PACKAGE, CALLING_FEATURE, true)).isEqualTo(2);
+        mContextFixture.removeCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        // Test getActiveSubscriptionInfo
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfo(
+                subId1, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId()).isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfo(
+                subId2, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId()).isEqualTo(subId2);
+        // Test getActiveSubscriptionInfoForIccId
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForIccId(
+                FAKE_ICCID1, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
+                .isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForIccId(
+                FAKE_ICCID2, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
+                .isEqualTo(subId2);
+        // Test getActiveSubscriptionInfoForSimSlotIndex
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForSimSlotIndex(
+                0, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
+                .isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForSimSlotIndex(
+                1, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
+                .isEqualTo(subId2);
+        // Test getActiveSubscriptionInfoList - forAllProfiles: false
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
+                        CALLING_PACKAGE, CALLING_FEATURE, false).stream()
+                .map(SubscriptionInfo::getSubscriptionId)
+                .toList()).isEqualTo(List.of(subId1));
+        // Test getActiveSubscriptionInfoList - forAllProfiles: true
+        assertThrows(SecurityException.class,
+                () -> mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
+                        CALLING_PACKAGE, CALLING_FEATURE, true));
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
+                        CALLING_PACKAGE, CALLING_FEATURE, true)
+                .stream().map(SubscriptionInfo::getSubscriptionId)
+                .toList()).isEqualTo(List.of(subId1, subId2));
+        mContextFixture.removeCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_PROFILES);
+        // Test getAllSubInfoList
+        assertThat(mSubscriptionManagerServiceUT.getAllSubInfoList(CALLING_PACKAGE,
+                CALLING_FEATURE).stream().map(SubscriptionInfo::getSubscriptionId).toList())
+                .isEqualTo(List.of(subId1));
+        // Test getAvailableSubscriptionInfoList
+        assertThat(mSubscriptionManagerServiceUT.getAvailableSubscriptionInfoList(CALLING_PACKAGE,
+                CALLING_FEATURE).stream().map(SubscriptionInfo::getSubscriptionId).toList())
+                .isEqualTo(List.of(subId1, subId2));
+        // Test getDefaultDataSubId
+        assertThat(mSubscriptionManagerServiceUT.getDefaultDataSubId()).isEqualTo(subId1);
+        // Test getDefault<Sms/Voice>SubIdAsUser
+        assertThat(mSubscriptionManagerServiceUT.getDefaultSmsSubIdAsUser(
+                FAKE_USER_HANDLE.getIdentifier())).isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getDefaultSubIdAsUser(
+                FAKE_USER_HANDLE.getIdentifier())).isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getDefaultVoiceSubIdAsUser(
+                FAKE_USER_HANDLE.getIdentifier())).isEqualTo(subId1);
+        // Test getEnabledSubscriptionId
+        assertThat(mSubscriptionManagerServiceUT.getEnabledSubscriptionId(0)).isEqualTo(
+                subId1);
+        assertThat(mSubscriptionManagerServiceUT.getEnabledSubscriptionId(1)).isEqualTo(
+                subId2);
+        // Test getOpportunisticSubscriptions
+        mSubscriptionManagerServiceUT.setOpportunistic(true, subId1, CALLING_PACKAGE);
+        mSubscriptionManagerServiceUT.setOpportunistic(true, subId2, CALLING_PACKAGE);
+        processAllMessages();
+        assertThat(mSubscriptionManagerServiceUT.getOpportunisticSubscriptions(CALLING_PACKAGE,
+                CALLING_FEATURE).stream().map(SubscriptionInfo::getSubscriptionId).toList())
+                .isEqualTo(List.of(subId1, subId2));
+        // Test getSubscriptionInfo - can get both as it's an internal getter
+        assertThat(mSubscriptionManagerServiceUT.getSubscriptionInfo(subId1).getSubscriptionId())
+                .isEqualTo(subId1);
+        assertThat(mSubscriptionManagerServiceUT.getSubscriptionInfo(subId2).getSubscriptionId())
+                .isEqualTo(subId2);
+        // Test getSubscriptionInfoListAssociatedWithUser
+        mContextFixture.addCallingOrSelfPermission(
+                Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
+        assertThat(mSubscriptionManagerServiceUT.getSubscriptionInfoListAssociatedWithUser(
+                        FAKE_USER_HANDLE).stream().map(SubscriptionInfo::getSubscriptionId)
+                .toList()).isEqualTo(List.of(subId1));
+        // Test getSubscriptionsInGroup
+        setCarrierPrivilegesForSubId(true, subId1);
+        assertThat(mSubscriptionManagerServiceUT.getSubscriptionsInGroup(
+                        ParcelUuid.fromString(FAKE_UUID1), CALLING_PACKAGE, CALLING_FEATURE)
+                .stream().map(SubscriptionInfo::getSubscriptionId).toList())
+                .isEqualTo(List.of(subId1));
+        // Test isActiveSubId
+        assertThat(mSubscriptionManagerServiceUT.isActiveSubId(subId1, CALLING_PACKAGE,
+                CALLING_FEATURE)).isTrue();
+        assertThat(mSubscriptionManagerServiceUT.isActiveSubId(subId2, CALLING_PACKAGE,
+                CALLING_FEATURE)).isTrue();
+        // Test isSubscriptionEnabled
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionEnabled(subId1)).isTrue();
+        assertThat(mSubscriptionManagerServiceUT.isSubscriptionEnabled(subId2)).isTrue();
+    }
+
+    @Test
     public void testSetUsageSetting() {
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
 
@@ -1193,7 +1657,12 @@
         assertThat(mSubscriptionManagerServiceUT.getOpportunisticSubscriptions(
                 CALLING_PACKAGE, CALLING_FEATURE)).isEmpty();
 
+        // Grant READ_PHONE_STATE permission
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        // Allow the application to perform.
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(),
+                        nullable(String.class), nullable(String.class), nullable(String.class));
 
         setIdentifierAccess(true);
         setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
@@ -1823,7 +2292,7 @@
         assertThat(mSubscriptionManagerServiceUT.getAllSubInfoList(
                 CALLING_PACKAGE, CALLING_FEATURE).isEmpty()).isTrue();
         assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
-                CALLING_PACKAGE, CALLING_FEATURE)).isEmpty();
+                CALLING_PACKAGE, CALLING_FEATURE, true)).isEmpty();
     }
 
     @Test
@@ -1942,6 +2411,7 @@
     }
 
     @Test
+    @DisableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
     public void testGetPhoneNumber() {
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         testSetPhoneNumber();
@@ -2029,7 +2499,7 @@
         verify(mEuiccController).blockingGetEuiccProfileInfoList(eq(1));
 
         List<SubscriptionInfo> subInfoList = mSubscriptionManagerServiceUT
-                .getActiveSubscriptionInfoList(CALLING_PACKAGE, CALLING_FEATURE);
+                .getActiveSubscriptionInfoList(CALLING_PACKAGE, CALLING_FEATURE, true);
         assertThat(subInfoList).hasSize(1);
         assertThat(subInfoList.get(0).getSimSlotIndex()).isEqualTo(1);
         assertThat(subInfoList.get(0).getSubscriptionId()).isEqualTo(1);
@@ -2147,7 +2617,7 @@
         processAllMessages();
 
         List<SubscriptionInfo> subInfoList = mSubscriptionManagerServiceUT
-                .getActiveSubscriptionInfoList(CALLING_PACKAGE, CALLING_FEATURE);
+                .getActiveSubscriptionInfoList(CALLING_PACKAGE, CALLING_FEATURE, true);
 
         assertThat(subInfoList).hasSize(1);
         assertThat(subInfoList.get(0).isActive()).isTrue();
@@ -2267,16 +2737,17 @@
                 CALLING_PACKAGE, CALLING_FEATURE)).isEmpty();
         assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false)).isEmpty();
         assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
-                CALLING_PACKAGE, CALLING_FEATURE)).isEmpty();
+                CALLING_PACKAGE, CALLING_FEATURE, true)).isEmpty();
 
         setIdentifierAccess(true);
         mSubscriptionManagerServiceUT.addSubInfo(FAKE_MAC_ADDRESS2, FAKE_CARRIER_NAME2,
                 0, SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
         assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false)).isNotEmpty();
         assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
-                CALLING_PACKAGE, CALLING_FEATURE)).isNotEmpty();
+                CALLING_PACKAGE, CALLING_FEATURE, true)).isNotEmpty();
         assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
-                CALLING_PACKAGE, CALLING_FEATURE).get(0).getIccId()).isEqualTo(FAKE_MAC_ADDRESS2);
+                CALLING_PACKAGE, CALLING_FEATURE, true).get(0).getIccId())
+                .isEqualTo(FAKE_MAC_ADDRESS2);
     }
 
     @Test
@@ -2544,4 +3015,156 @@
         assertThat(subInfo.isRemovableEmbedded()).isFalse();
         assertThat(subInfo.getNativeAccessRules()).isEqualTo(new byte[]{});
     }
+
+    @Test
+    public void testGetActiveSubscriptionInfoListNoSecurityException() {
+        // Grant MODIFY_PHONE_STATE permission for insertion.
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+        insertSubscription(new SubscriptionInfoInternal.Builder(FAKE_SUBSCRIPTION_INFO2)
+                .setSimSlotIndex(SubscriptionManager.INVALID_SIM_SLOT_INDEX).build());
+        // Remove MODIFY_PHONE_STATE
+        mContextFixture.removeCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+
+        // Should get an empty list without READ_PHONE_STATE.
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
+                CALLING_PACKAGE, CALLING_FEATURE, true)).isEmpty();
+
+        // Grant READ_PHONE_STATE permission for insertion.
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        // Disallow the application to perform.
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOpsManager)
+                .noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(),
+                        nullable(String.class), nullable(String.class), nullable(String.class));
+
+        // Should get an empty list if the application is not allowed to perform it.
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoList(
+                CALLING_PACKAGE, CALLING_FEATURE, true)).isEmpty();
+    }
+
+    @Test
+    public void testUpdateGroupDisabled() {
+        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+        insertSubscription(new SubscriptionInfoInternal
+                .Builder(FAKE_SUBSCRIPTION_INFO2).setGroupUuid(FAKE_UUID1).build());
+
+        mSubscriptionManagerServiceUT.updateGroupDisabled();
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(2);
+        assertThat(subInfo.isGroupDisabled()).isFalse();
+    }
+
+    @Test
+    public void testIsSatelliteSpn() {
+        mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
+                FAKE_CARRIER_NAME1);
+        System.setProperty("persist.radio.allow_mock_modem", "true");
+        doReturn(true).when(mFlags).oemEnabledSatelliteFlag();
+
+        EuiccProfileInfo profileInfo1 = new EuiccProfileInfo.Builder(FAKE_ICCID1)
+                .setIccid(FAKE_ICCID1)
+                .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(1);
+
+        mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
+                FAKE_CARRIER_NAME1);
+        System.setProperty("persist.radio.allow_mock_modem", "false");
+        doReturn(false).when(mFlags).oemEnabledSatelliteFlag();
+    }
+
+    @Test
+    public void testIsSatelliteSpnWithNullCarrierIdentifier() {
+        mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
+                FAKE_CARRIER_NAME1);
+        System.setProperty("persist.radio.allow_mock_modem", "true");
+        doReturn(true).when(mFlags).oemEnabledSatelliteFlag();
+
+        EuiccProfileInfo profileInfo1 = new EuiccProfileInfo.Builder(FAKE_ICCID1)
+                .setIccid(FAKE_ICCID1)
+                .setNickname(FAKE_CARRIER_NAME1)
+                .setServiceProviderName(FAKE_CARRIER_NAME1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_OPERATIONAL)
+                .setCarrierIdentifier(null)
+                .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(1);
+
+        mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
+                FAKE_CARRIER_NAME1);
+        System.setProperty("persist.radio.allow_mock_modem", "false");
+        doReturn(false).when(mFlags).oemEnabledSatelliteFlag();
+    }
+
+    @Test
+    public void testIsSatelliteSpnWithWrongSpn() {
+        mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
+                FAKE_CARRIER_NAME1);
+        System.setProperty("persist.radio.allow_mock_modem", "true");
+        doReturn(true).when(mFlags).oemEnabledSatelliteFlag();
+
+        EuiccProfileInfo profileInfo1 = new EuiccProfileInfo.Builder(FAKE_ICCID1)
+                .setIccid(FAKE_ICCID1)
+                .setNickname(FAKE_CARRIER_NAME1)
+                .setServiceProviderName(FAKE_CARRIER_NAME2)
+                .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(0);
+
+        mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
+                FAKE_CARRIER_NAME1);
+        System.setProperty("persist.radio.allow_mock_modem", "false");
+        doReturn(false).when(mFlags).oemEnabledSatelliteFlag();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/AnswerToResetTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/AnswerToResetTest.java
index 3dba439..2ae08ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/AnswerToResetTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/AnswerToResetTest.java
@@ -21,14 +21,13 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 
-
 public class AnswerToResetTest {
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardStatusTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardStatusTest.java
index 5d759c2..f035b77 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardStatusTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardStatusTest.java
@@ -18,9 +18,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
index 143a530..53627ca 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
@@ -29,7 +29,8 @@
 import android.os.AsyncResult;
 import android.os.HandlerThread;
 import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.IccPhoneBookInterfaceManager;
 import com.android.internal.telephony.IccProvider;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java
index 59bb239..d2490ef 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java
@@ -28,10 +28,11 @@
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.test.SimulatedCommands;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
index e4fabc5..33b195c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
@@ -21,10 +21,11 @@
 import static org.mockito.Mockito.mock;
 
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
index 15fb729..9a444d7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
@@ -28,10 +28,11 @@
 import android.os.AsyncResult;
 import android.os.Message;
 import android.telephony.UiccAccessRule;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.TelephonyTest;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
index 2ab23f3..9265a62 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
@@ -495,40 +495,6 @@
         assertEquals(uiccCardInfo, mUiccControllerUT.getAllUiccCardInfos().get(0));
     }
 
-    @Test
-    public void testEidNotSupported() {
-        // Give UiccController a real context so it can use shared preferences
-        mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
-
-        // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
-        doReturn(true).when(mMockSlot).isEuicc();
-        doReturn(mMockEuiccCard).when(mMockSlot).getUiccCard();
-        doReturn(null).when(mMockEuiccCard).getEid();
-
-        // simulate card status loaded so that the UiccController sets the card ID
-        IccCardStatus ics = new IccCardStatus();
-        ics.setCardState(1 /* present */);
-        ics.setUniversalPinState(3 /* disabled */);
-        ics.atr = "abcdef0123456789abcdef";
-        ics.iccid = "123451234567890";
-        ics.mSlotPortMapping = new IccSlotPortMapping();
-        ics.mSlotPortMapping.mPhysicalSlotIndex = UiccController.INVALID_SLOT_ID;
-        // make it seem like EID is not supported by setting physical slot = -1 like on HAL < 1.2
-
-        mSimulatedCommands.setSupportsEid(false);
-
-        AsyncResult ar = new AsyncResult(null, ics, null);
-        Message msg = Message.obtain(mUiccControllerUT, EVENT_GET_ICC_STATUS_DONE, ar);
-        mUiccControllerUT.handleMessage(msg);
-
-        // assert that the default eUICC card Id is UNSUPPORTED_CARD_ID
-        assertEquals(TelephonyManager.UNSUPPORTED_CARD_ID,
-                mUiccControllerUT.getCardIdForDefaultEuicc());
-
-        mSimulatedCommands.setSupportsEid(true);
-    }
-
     /**
      * The default eUICC should not be the removable slot if there is a built-in eUICC.
      */
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 14e95f1..1a846c4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
@@ -26,10 +26,11 @@
 
 import android.os.Binder;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.telephony.IccLogicalChannelRequest;
 import com.android.internal.telephony.TelephonyTest;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
index a9034eb..ca322e0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
@@ -129,7 +129,7 @@
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
         mUiccProfile = new UiccProfile(mContext, mSimulatedCommands, mIccCardStatus,
-              0 /* phoneId */, mUiccCard, new Object());
+              0 /* phoneId */, mUiccCard, new Object(), mFeatureFlags);
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
         mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
index 230f147..671f273 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
@@ -42,7 +42,6 @@
 import org.junit.Test;
 
 public class UiccSlotTest extends TelephonyTest {
-    private UiccSlot mUiccSlot;
     private UiccSlotTestHandlerThread mTestHandlerThread;
     private Handler mTestHandler;
 
@@ -263,7 +262,7 @@
 
         // assert on updated values
         assertTrue(mUiccSlot.isActive());
-        assertEquals(mUiccSlot.getMinimumVoltageClass(), UiccSlot.VOLTAGE_CLASS_A);
+        assertEquals(UiccSlot.VOLTAGE_CLASS_A, mUiccSlot.getMinimumVoltageClass());
     }
 
     @Test
@@ -429,7 +428,7 @@
         mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex);
         verify(mTelephonyComponentFactory).makeUiccProfile(
                 anyObject(), eq(mSimulatedCommands), eq(mIccCardStatus), anyInt(), anyObject(),
-                anyObject());
+                anyObject(), anyObject());
         assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccSlot.getCardState());
         assertNotNull(mUiccSlot.getUiccCard());
 
@@ -452,7 +451,7 @@
         mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex);
         verify(mTelephonyComponentFactory).makeUiccProfile(
                 anyObject(), eq(mSimulatedCommands), eq(mIccCardStatus), anyInt(), anyObject(),
-                anyObject());
+                anyObject(), anyObject());
         assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccSlot.getCardState());
         assertNotNull(mUiccSlot.getUiccCard());
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
index 7e51bad..8209dfa 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
@@ -33,7 +33,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
 import com.android.internal.telephony.TelephonyIntents;
@@ -50,7 +51,6 @@
     private static final String PROVISIONING_PACKAGE_NAME = "test.provisioning.package";
 
     // Mocked classes
-    private Context mContext;
     private Resources mResources;
 
     private IccCardStatus makeCardStatus(CardState state) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
index b6dd7bd..c38be60 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
@@ -52,7 +52,6 @@
 
     private static class ResultCaptor<T> extends AsyncResultCallback<T> {
         public T result;
-        public Throwable exception;
 
         private CountDownLatch mLatch;
 
@@ -68,7 +67,6 @@
 
         @Override
         public void onException(Throwable e) {
-            exception = e;
             mLatch.countDown();
         }
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/Asn1DecoderTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/Asn1DecoderTest.java
index cfd8b99..c02f965 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/Asn1DecoderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/Asn1DecoderTest.java
@@ -23,7 +23,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.uicc.asn1.Asn1Decoder;
 import com.android.internal.telephony.uicc.asn1.Asn1Node;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/Asn1NodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/Asn1NodeTest.java
index e987a1f..27f8940 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/Asn1NodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/Asn1NodeTest.java
@@ -22,7 +22,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.asn1.Asn1Decoder;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/IccUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/IccUtilsTest.java
index 9c1dd20..d0b455a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/IccUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/asn1/IccUtilsTest.java
@@ -20,7 +20,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.uicc.IccUtils;