[automerger skipped] Merge "Merge Android 14 QPR2 to AOSP main" into main am: 492983e9a0 -s ours am: f823f96ebb -s ours

am skip reason: Merged-In I866f9364f94a09466f6e021aa3b0f4f50a14f656 with SHA-1 84ea42d204 is already in history

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

Change-Id: I86d939f8d57ae6e050f9209be4c798d91214ae64
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 121236a..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: [
@@ -108,6 +107,7 @@
         "modules-utils-build_system",
         "modules-utils-fastxmlserializer",
         "modules-utils-statemachine",
+        "services-config-update",
     ],
 
     optimize: {
diff --git a/flags/Android.bp b/flags/Android.bp
index 3c0deee..8f363b6 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -22,16 +22,17 @@
     name: "telephony_flags",
     package: "com.android.internal.telephony.flags",
     srcs: [
-      "data.aconfig",
-      "domainselection.aconfig",
-      "ims.aconfig",
-      "messaging.aconfig",
-      "misc.aconfig",
-      "network.aconfig",
-      "subscription.aconfig",
-      "uicc.aconfig",
-      "satellite.aconfig",
-      "iwlan.aconfig",
-      "telephony.aconfig",
+        "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..f8de8b2
--- /dev/null
+++ b/flags/calling.aconfig
@@ -0,0 +1,16 @@
+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"
+  is_exported: true
+}
+
+flag {
+  name: "show_call_fail_notification_for_2g_toggle"
+  namespace: "telephony"
+  description: "Used in DisconnectCause and TelephonyConnection if a non-emergency call fails on a device with no 2G, to guard whether a user can see an updated error message reminding the 2G is disabled and potentially disrupting their call connectivity"
+  bug: "300142897"
+}
\ No newline at end of file
diff --git a/flags/data.aconfig b/flags/data.aconfig
index 267048e..6334803 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -1,10 +1,13 @@
 package: "com.android.internal.telephony.flags"
 
 flag {
-  name: "auto_switch_allow_roaming"
+  name: "auto_data_switch_allow_roaming"
   namespace: "telephony"
   description: "Allow using roaming network as target if user allows it from settings."
-  bug: "306488039"
+  bug: "287132491"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
 flag {
@@ -90,3 +93,38 @@
   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"
+}
+
+flag {
+  name: "reconnect_qualified_network"
+  namespace: "telephony"
+  description: "This flag is for internal implementation to handle reconnect request from QNS in telephony FWK."
+  bug: "319520561"
+}
+
+flag {
+  name: "dsrs_diagnostics_enabled"
+  namespace: "telephony"
+  description: "Enable DSRS diagnostics."
+  bug: "319601607"
+}
\ No newline at end of file
diff --git a/flags/ims.aconfig b/flags/ims.aconfig
index 4638194..b2cc904 100644
--- a/flags/ims.aconfig
+++ b/flags/ims.aconfig
@@ -41,3 +41,31 @@
     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"
+}
+
+flag {
+    name: "enable_sip_subscribe_retry"
+    namespace: "telephony"
+    description: "This flag controls whether framework supports SIP subscribe retry or not"
+    bug:"297023230"
+}
diff --git a/flags/misc.aconfig b/flags/misc.aconfig
index 835efee..aabceca 100644
--- a/flags/misc.aconfig
+++ b/flags/misc.aconfig
@@ -65,8 +65,28 @@
 }
 
 flag {
-  name: "radio_info_is_radio_on"
-  namespace: "telephony"
-  description: "change method to show mobile radio power from service state to radio power"
-  bug: "306084899"
+    name: "reset_mobile_network_settings"
+    namespace: "telephony"
+    description: "Allows applications to launch Reset Mobile Network Settings page in Settings app."
+    bug:"271921464"
+}
+
+flag {
+    name: "fix_crash_on_getting_config_when_phone_is_gone"
+    namespace: "telephony"
+    description: "Fix VCN crash when calling CarrierConfigManager.getConfigForSubId while phone process has gone."
+    bug:"319791612"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "add_anomaly_when_notify_config_changed_with_invalid_phone"
+    namespace: "telephony"
+    description: "Report anomaly when CarrierConfigLoader received config change with sub that maps to invalid phoneId"
+    bug:"270757342"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
diff --git a/flags/network.aconfig b/flags/network.aconfig
index 27489f4..c0394e8 100644
--- a/flags/network.aconfig
+++ b/flags/network.aconfig
@@ -17,6 +17,48 @@
 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/subscription.aconfig b/flags/subscription.aconfig
index 0521cb9..cebedd5 100644
--- a/flags/subscription.aconfig
+++ b/flags/subscription.aconfig
@@ -5,4 +5,39 @@
   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
index b849d53..9ef70b1 100644
--- a/flags/telephony.aconfig
+++ b/flags/telephony.aconfig
@@ -13,3 +13,31 @@
     description: "This flag controls telephony feature flags mapping for public APIs and CTS."
     bug:"297989574"
 }
+
+flag {
+    name: "prevent_system_server_and_phone_deadlock"
+    namespace: "telephony"
+    description: "This flag controls the order of the binder to prevent deadlock in system_server"
+    bug: "315973270"
+}
+
+flag {
+    name: "prevent_invocation_repeat_of_ril_call_when_device_does_not_support_voice"
+    namespace: "telephony"
+    description: "This flag prevents repeat invocation of call related APIs in RIL when the device is not voice capable"
+    bug: "290833783"
+}
+
+flag {
+    name: "minimal_telephony_cdm_check"
+    namespace: "telephony"
+    description: "This flag disables Calling/Data/Messaging features if their respective feature is missing"
+    bug: "310710841"
+}
+
+flag {
+    name: "minimal_telephony_managers_conditional_on_features"
+    namespace: "telephony"
+    description: "This flag enables checking for telephony features before initializing corresponding managers"
+    bug: "310710841"
+}
diff --git a/flags/uicc.aconfig b/flags/uicc.aconfig
index b2024b0..c1b860f 100644
--- a/flags/uicc.aconfig
+++ b/flags/uicc.aconfig
@@ -15,6 +15,18 @@
 flag {
     name: "carrier_restriction_status"
     namespace: "telephony"
-    description: "This flag control the visibility of the getCarrierRestrictionStatus in carrierRestrictionRules class."
+    description: "This flag controls the visibility of the getCarrierRestrictionStatus in carrierRestrictionRules class."
     bug:"313553044"
-}
\ No newline at end of file
+}
+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 d7a6062..c07c797 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -421,6 +421,7 @@
 }
 
 message ImsRegistrationStats {
+    reserved 16;
     optional int32 carrier_id = 1;
     optional int32 sim_slot_index = 2;
     optional int32 rat = 3;
@@ -437,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;
diff --git a/proto/src/telephony_config_update.proto b/proto/src/telephony_config_update.proto
new file mode 100644
index 0000000..c193f35
--- /dev/null
+++ b/proto/src/telephony_config_update.proto
@@ -0,0 +1,46 @@
+
+// Copyright 2024 Google LLC
+//
+// 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.
+
+syntax = "proto2";
+package com.android.internal.telephony.satellite;
+
+option java_package = "com.android.internal.telephony.satellite";
+option java_outer_classname = "SatelliteConfigData";
+
+message TelephonyConfigProto {
+  optional SatelliteConfigProto satellite = 1;
+}
+
+message SatelliteConfigProto {
+  optional int32 version = 1;
+  repeated CarrierSupportedSatelliteServicesProto carrier_supported_satellite_services = 2;
+  optional SatelliteRegionProto device_satellite_region = 3;
+}
+
+message CarrierSupportedSatelliteServicesProto {
+  optional int32 carrier_id = 1;
+  repeated SatelliteProviderCapabilityProto supported_satellite_provider_capabilities = 2;
+}
+
+message SatelliteProviderCapabilityProto{
+  optional string carrier_plmn = 1;
+  repeated int32 allowed_services = 2;
+}
+
+message SatelliteRegionProto {
+  optional bytes s2_cell_file = 1;
+  repeated string country_codes = 2;
+  optional bool is_allowed = 3;
+}
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index b33b732..6f66545 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -120,6 +120,7 @@
     protected RegistrantList mTriggerImsDeregistrationRegistrants = new RegistrantList();
     protected RegistrantList mImeiInfoRegistrants = new RegistrantList();
     protected RegistrantList mCellularIdentifierDisclosedRegistrants = new RegistrantList();
+    protected RegistrantList mSecurityAlgorithmUpdatedRegistrants = new RegistrantList();
 
     @UnsupportedAppUsage
     protected Registrant mGsmSmsRegistrant;
@@ -1194,4 +1195,14 @@
     public void unregisterForCellularIdentifierDisclosures(Handler h) {
         mCellularIdentifierDisclosedRegistrants.remove(h);
     }
+
+    @Override
+    public void registerForSecurityAlgorithmUpdates(Handler h, int what, Object obj) {
+        mSecurityAlgorithmUpdatedRegistrants.add(h, what, obj);
+    }
+
+    @Override
+    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/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/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index 91e6fab..ee7447c 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -2940,4 +2940,16 @@
      * @param h Handler to be removed from the registrant list.
      */
     default void unregisterForCellularIdentifierDisclosures(@NonNull Handler h) {}
+
+    /**
+     * Registers for security algorithm update events.
+     */
+    default void registerForSecurityAlgorithmUpdates(Handler h, int what, Object obj) {}
+
+    /**
+     * Unregisters for security algorithm update events.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    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 c035329..e81d0f1 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -45,6 +45,7 @@
 import com.android.telephony.Rlog;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * broadcast intents
@@ -301,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/DisplayInfoController.java b/src/java/com/android/internal/telephony/DisplayInfoController.java
index 945b640..e8a0566 100644
--- a/src/java/com/android/internal/telephony/DisplayInfoController.java
+++ b/src/java/com/android/internal/telephony/DisplayInfoController.java
@@ -108,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);
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index d76ee19..9113514 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Bundle;
@@ -47,6 +48,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 +157,15 @@
 
     //***** Constructors
 
-    public GsmCdmaCallTracker (GsmCdmaPhone phone) {
+    public GsmCdmaCallTracker(GsmCdmaPhone phone, FeatureFlags featureFlags) {
+        super(featureFlags);
+
+        if (mFeatureFlags.minimalTelephonyCdmCheck()
+                && !phone.getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_TELEPHONY_CALLING)) {
+            throw new UnsupportedOperationException("GsmCdmaCallTracker requires calling");
+        }
+
         this.mPhone = phone;
         mCi = phone.mCi;
         mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
@@ -1489,7 +1499,7 @@
 
         switch (msg.what) {
             case EVENT_POLL_CALLS_RESULT:
-                Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");
+                if (DBG_POLL) Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");
 
                 if (msg == mLastRelevantPoll) {
                     if (DBG_POLL) log(
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 620b871..de7ebd6 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;
@@ -45,6 +42,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.database.SQLException;
 import android.hardware.radio.modem.ImeiInfo;
 import android.net.Uri;
@@ -75,7 +73,6 @@
 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;
@@ -86,6 +83,7 @@
 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;
@@ -95,6 +93,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;
 
@@ -118,6 +117,8 @@
 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;
@@ -148,7 +149,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;
@@ -255,6 +255,7 @@
 
     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 &
@@ -301,14 +302,24 @@
     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 boolean mIsN1ModeAllowedByCarrier = true;
+    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 boolean mIsN1ModeAllowedByIms = true;
+    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;
@@ -360,9 +371,11 @@
                 SignalStrengthController.class.getName()).makeSignalStrengthController(this);
         mSST = mTelephonyComponentFactory.inject(ServiceStateTracker.class.getName())
                 .makeServiceStateTracker(this, this.mCi, featureFlags);
-        mEmergencyNumberTracker = mTelephonyComponentFactory
-                .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker(
-                        this, this.mCi);
+        if (hasCalling()) {
+            mEmergencyNumberTracker = mTelephonyComponentFactory
+                    .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker(
+                            this, this.mCi, mFeatureFlags);
+        }
         mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
                 .makeDeviceStateMonitor(this, mFeatureFlags);
 
@@ -402,9 +415,11 @@
 
         mCallWaitingController = new CallWaitingController(this);
 
-        loadTtyMode();
+        if (hasCalling()) {
+            loadTtyMode();
 
-        CallManager.getInstance().registerPhone(this);
+            CallManager.getInstance().registerPhone(this);
+        }
 
         mSubscriptionsChangedListener =
                 new SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -454,13 +469,21 @@
         }
     };
 
+    private boolean hasCalling() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+            PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
+
     private void initOnce(CommandsInterface ci) {
         if (ci instanceof SimulatedRadioControl) {
             mSimulatedRadioControl = (SimulatedRadioControl) ci;
         }
 
-        mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
-                .makeGsmCdmaCallTracker(this);
+        if (hasCalling()) {
+            mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
+                    .makeGsmCdmaCallTracker(this, mFeatureFlags);
+        }
         mIccPhoneBookIntManager = mTelephonyComponentFactory
                 .inject(IccPhoneBookInterfaceManager.class.getName())
                 .makeIccPhoneBookInterfaceManager(this);
@@ -521,19 +544,38 @@
 
         mCi.registerForImeiMappingChanged(this, EVENT_IMEI_MAPPING_CHANGED, null);
 
-        if (mFeatureFlags.enableIdentifierDisclosureTransparency()) {
+        if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
+                || mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+            mSafetySource =
+                    mTelephonyComponentFactory.makeCellularNetworkSecuritySafetySource(mContext);
+        }
+
+        if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()) {
             logi(
-                    "enable_identifier_disclosure_transparency is on. Registering for cellular "
-                            + "identifier disclosures from phone "
+                    "enable_identifier_disclosure_transparency_unsol_events is on. Registering for "
+                            + "cellular identifier disclosures from phone "
                             + getPhoneId());
             mIdentifierDisclosureNotifier =
                     mTelephonyComponentFactory
                             .inject(CellularIdentifierDisclosureNotifier.class.getName())
-                            .makeIdentifierDisclosureNotifier();
+                            .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(mSafetySource);
+            mCi.registerForSecurityAlgorithmUpdates(
+                    this, EVENT_SECURITY_ALGORITHM_UPDATE, null);
+        }
+
         initializeCarrierApps();
     }
 
@@ -664,7 +706,7 @@
         unregisterForIccRecordEvents();
         registerForIccRecordEvents();
 
-        mCT.updatePhoneType();
+        if (mCT != null) mCT.updatePhoneType();
 
         int radioState = mCi.getRadioState();
         if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
@@ -724,6 +766,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public PhoneConstants.State getState() {
+        if (!hasCalling()) return PhoneConstants.State.IDLE;
+
         if (mImsPhone != null) {
             PhoneConstants.State imsState = mImsPhone.getState();
             if (imsState != PhoneConstants.State.IDLE) {
@@ -808,6 +852,7 @@
 
     @Override
     public boolean isDataSuspended() {
+        if (mCT == null) return false;
         return mCT.mState != PhoneConstants.State.IDLE && !mSST.isConcurrentVoiceAndDataAllowed();
     }
 
@@ -855,7 +900,7 @@
 
     @Override
     public boolean isInEmergencyCall() {
-        if (isPhoneTypeGsm()) {
+        if (!hasCalling() || isPhoneTypeGsm()) {
             return false;
         } else {
             return mCT.isInEmergencyCall();
@@ -864,7 +909,7 @@
 
     @Override
     protected void setIsInEmergencyCall() {
-        if (!isPhoneTypeGsm()) {
+        if (!hasCalling() && !isPhoneTypeGsm()) {
             mCT.setIsInEmergencyCall();
         }
     }
@@ -956,6 +1001,7 @@
 
     @Override
     public void acceptCall(int videoState) throws CallStateException {
+        if (!hasCalling()) throw new CallStateException();
         Phone imsPhone = mImsPhone;
         if ( imsPhone != null && imsPhone.getRingingCall().isRinging() ) {
             imsPhone.acceptCall(videoState);
@@ -966,6 +1012,7 @@
 
     @Override
     public void rejectCall() throws CallStateException {
+        if (!hasCalling()) throw new CallStateException();
         mCT.rejectCall();
     }
 
@@ -996,6 +1043,7 @@
 
     @Override
     public boolean canConference() {
+        if (!hasCalling()) return false;
         if (mImsPhone != null && mImsPhone.canConference()) {
             return true;
         }
@@ -1046,12 +1094,13 @@
 
     @Override
     public void clearDisconnected() {
+        if (!hasCalling()) return;
         mCT.clearDisconnected();
     }
 
     @Override
     public boolean canTransfer() {
-        if (isPhoneTypeGsm()) {
+        if (hasCalling() && isPhoneTypeGsm()) {
             return mCT.canTransfer();
         } else {
             loge("canTransfer: not possible in CDMA");
@@ -1061,7 +1110,7 @@
 
     @Override
     public void explicitCallTransfer() {
-        if (isPhoneTypeGsm()) {
+        if (hasCalling() && isPhoneTypeGsm()) {
             mCT.explicitCallTransfer();
         } else {
             loge("explicitCallTransfer: not possible in CDMA");
@@ -1075,11 +1124,13 @@
 
     @Override
     public GsmCdmaCall getBackgroundCall() {
+        if (!hasCalling()) return null;
         return mCT.mBackgroundCall;
     }
 
     @Override
     public Call getRingingCall() {
+        if (!hasCalling()) return null;
         Phone imsPhone = mImsPhone;
         // It returns the ringing call of ImsPhone if the ringing call of GSMPhone isn't ringing.
         // In CallManager.registerPhone(), it always registers ringing call of ImsPhone, because
@@ -1155,7 +1206,7 @@
 
     private boolean handleCallDeflectionIncallSupplementaryService(
             String dialString) {
-        if (dialString.length() > 1) {
+        if (!hasCalling() || dialString.length() > 1) {
             return false;
         }
 
@@ -1180,7 +1231,7 @@
     private boolean handleCallWaitingIncallSupplementaryService(String dialString) {
         int len = dialString.length();
 
-        if (len > 2) {
+        if (!hasCalling() || len > 2) {
             return false;
         }
 
@@ -1400,6 +1451,9 @@
     @Override
     public Connection dial(String dialString, @NonNull DialArgs dialArgs,
             Consumer<Phone> chosenPhoneConsumer) throws CallStateException {
+        if (!hasCalling()) {
+            throw new CallStateException("Calling feature is not supported!");
+        }
         if (!isPhoneTypeGsm() && dialArgs.uusInfo != null) {
             throw new CallStateException("Sending UUS information NOT supported in CDMA!");
         }
@@ -1469,24 +1523,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);
@@ -1825,8 +1861,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;
                 }
@@ -2134,7 +2173,9 @@
 
     @Override
     public int getEmergencyNumberDbVersion() {
-        return getEmergencyNumberTracker().getEmergencyNumberDbVersion();
+        EmergencyNumberTracker tracker = getEmergencyNumberTracker();
+        if (tracker == null) return -1;
+        return tracker.getEmergencyNumberDbVersion();
     }
 
     @Override
@@ -2406,7 +2447,11 @@
             // This might be called by IMS on another thread, so to avoid the requirement to
             // lock, post it through the handler.
             post(() -> {
-                mIsN1ModeAllowedByIms = enable;
+                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 {
@@ -2420,7 +2465,7 @@
 
     /** Only called on the handler thread. */
     private void maybeUpdateModemN1Mode(@Nullable Message result) {
-        final boolean wantN1Enabled = mIsN1ModeAllowedByCarrier && mIsN1ModeAllowedByIms;
+        final boolean wantN1Enabled = mN1ModeDisallowedReasons.isEmpty();
 
         logd("N1 Mode: isModemN1Enabled=" + mModemN1Mode + ", wantN1Enabled=" + wantN1Enabled);
 
@@ -2441,11 +2486,20 @@
     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);
 
-        mIsN1ModeAllowedByCarrier = ArrayUtils.contains(
-                supportedNrModes, CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA);
+
+        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 {
@@ -2580,7 +2634,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);
 
@@ -2833,13 +2887,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);
 
@@ -3105,6 +3161,8 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void syncClirSetting() {
+        if (!hasCalling()) return;
+
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         migrateClirSettingIfNeeded(sp);
 
@@ -3146,6 +3204,7 @@
 
         handleNullCipherEnabledChange();
         handleIdentifierDisclosureNotificationPreferenceChange();
+        handleNullCipherNotificationPreferenceChanged();
     }
 
     private void handleRadioOn() {
@@ -3309,8 +3368,10 @@
                 if (b != null) {
                     updateBroadcastEmergencyCallStateChangesAfterCarrierConfigChanged(b);
                     updateCdmaRoamingSettingsAfterCarrierConfigChanged(b);
-                    updateNrSettingsAfterCarrierConfigChanged(b);
-                    updateVoNrSettings(b);
+                    if (hasCalling()) {
+                        updateNrSettingsAfterCarrierConfigChanged(b);
+                        updateVoNrSettings(b);
+                    }
                     updateSsOverCdmaSupported(b);
                     updateCarrierN1ModeSupported(b);
                 } else {
@@ -3689,10 +3750,10 @@
                 }
 
                 CellularIdentifierDisclosure disclosure = (CellularIdentifierDisclosure) ar.result;
-                if (mFeatureFlags.enableIdentifierDisclosureTransparency()
+                if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
                         && mIdentifierDisclosureNotifier != null
                         && disclosure != null) {
-                    mIdentifierDisclosureNotifier.addDisclosure(disclosure);
+                    mIdentifierDisclosureNotifier.addDisclosure(mContext, getSubId(), disclosure);
                 }
                 break;
 
@@ -3702,6 +3763,22 @@
                 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(mContext, getSubId(), 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);
         }
@@ -4866,6 +4943,7 @@
      * Handler of RIL Voice Radio Technology changed event.
      */
     private void onVoiceRegStateOrRatChanged(int vrs, int vrat) {
+        if (!hasCalling()) return;
         logd("onVoiceRegStateOrRatChanged");
         mCT.dispatchCsCallRadioTech(getCsCallRadioTech(vrs, vrat));
     }
@@ -4961,7 +5039,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();
@@ -5067,6 +5145,8 @@
      * Load the current TTY mode in GsmCdmaPhone based on Telecom and UI settings.
      */
     private void loadTtyMode() {
+        if (!hasCalling()) return;
+
         int ttyMode = TelecomManager.TTY_MODE_OFF;
         TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
         if (telecomManager != null) {
@@ -5182,7 +5262,7 @@
     }
 
     private void updateVoNrSettings(@NonNull PersistableBundle config) {
-        if (mSimState != TelephonyManager.SIM_STATE_LOADED) {
+        if (getIccCard().getState() != IccCardConstants.State.LOADED) {
             return;
         }
 
@@ -5310,15 +5390,23 @@
     public void handleIdentifierDisclosureNotificationPreferenceChange() {
         if (!mFeatureFlags.enableIdentifierDisclosureTransparency()) {
             logi("Not handling identifier disclosure preference change. Feature flag "
-                    + "ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY disabled");
+                    + "enable_identifier_disclosure_transparency disabled");
             return;
         }
         boolean prefEnabled = getIdentifierDisclosureNotificationsPreferenceEnabled();
 
-        if (prefEnabled) {
-            mIdentifierDisclosureNotifier.enable();
+        // 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 {
-            mIdentifierDisclosureNotifier.disable();
+            logi("Not toggling enable state for disclosure notifier. Feature flag "
+                    + "enable_identifier_disclosure_transparency_unsol_events is disabled");
         }
 
         mCi.setCellularIdentifierTransparencyEnabled(prefEnabled,
@@ -5326,6 +5414,33 @@
     }
 
     @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(mContext);
+            } else {
+                mNullCipherNotifier.disable(mContext);
+            }
+        } 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;
     }
@@ -5334,4 +5449,17 @@
     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/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index 4e4d55d..4146c24 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -203,12 +203,12 @@
                         mTrackers.remove(token);
                         mPhone.notifySmsSent(tracker.mDestAddress);
                         mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
-                                tracker.mDestAddress, tracker.mMessageId);
+                                tracker.mDestAddress, tracker.mMessageId, true);
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR:
                         tracker.onFailed(mContext, reason, networkReasonCode);
                         mTrackers.remove(token);
-                        notifySmsSentFailedToEmergencyStateTracker(tracker);
+                        notifySmsSentFailedToEmergencyStateTracker(tracker, true);
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY:
                         int maxRetryCountOverIms = getMaxRetryCountOverIms();
@@ -227,7 +227,7 @@
                         } else {
                             tracker.onFailed(mContext, reason, networkReasonCode);
                             mTrackers.remove(token);
-                            notifySmsSentFailedToEmergencyStateTracker(tracker);
+                            notifySmsSentFailedToEmergencyStateTracker(tracker, true);
                         }
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK:
@@ -304,6 +304,11 @@
                     switch (result) {
                         case Intents.RESULT_SMS_HANDLED:
                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_OK;
+                            if (message != null) {
+                                mSmsDispatchersController
+                                        .notifySmsReceivedViaImsToEmergencyStateTracker(
+                                                message.getOriginatingAddress());
+                            }
                             break;
                         case Intents.RESULT_SMS_OUT_OF_MEMORY:
                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_NO_MEMORY;
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/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index d07e731..aaeba23 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -35,6 +35,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
@@ -52,6 +53,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.ArrayUtils;
@@ -122,6 +124,7 @@
 
     protected final Context mContext;
     private final SubscriptionManagerService mSubscriptionManagerService;
+    private final @NonNull FeatureFlags mFeatureFlags;
 
     // Keep a record of active primary (non-opportunistic) subscription list.
     @NonNull private List<Integer> mPrimarySubList = new ArrayList<>();
@@ -201,10 +204,11 @@
     /**
      * Init instance of MultiSimSettingController.
      */
-    public static MultiSimSettingController init(Context context) {
+    public static MultiSimSettingController init(Context context,
+            @NonNull FeatureFlags featureFlags) {
         synchronized (MultiSimSettingController.class) {
             if (sInstance == null) {
-                sInstance = new MultiSimSettingController(context);
+                sInstance = new MultiSimSettingController(context, featureFlags);
             } else {
                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
             }
@@ -213,9 +217,10 @@
     }
 
     @VisibleForTesting
-    public MultiSimSettingController(Context context) {
+    public MultiSimSettingController(Context context, @NonNull FeatureFlags featureFlags) {
         mContext = context;
         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+        mFeatureFlags = featureFlags;
 
         // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change.
         TelephonyManager telephonyManager = ((TelephonyManager) mContext.getSystemService(
@@ -239,6 +244,24 @@
                         onCarrierConfigChanged(slotIndex, subId));
     }
 
+    private boolean hasCalling() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
+
+    private boolean hasData() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_DATA);
+    }
+
+    private boolean hasMessaging() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING);
+    }
+
     /**
      * Notify MOBILE_DATA of a subscription is changed.
      */
@@ -420,22 +443,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();
     }
 
     /**
@@ -615,35 +629,43 @@
                 || mActiveModemCount == 1)) {
             int subId = mPrimarySubList.get(0);
             if (DBG) log("updateDefaultValues: to only primary sub " + subId);
-            mSubscriptionManagerService.setDefaultDataSubId(subId);
-            mSubscriptionManagerService.setDefaultVoiceSubId(subId);
-            mSubscriptionManagerService.setDefaultSmsSubId(subId);
+            if (hasData()) mSubscriptionManagerService.setDefaultDataSubId(subId);
+            if (hasCalling()) mSubscriptionManagerService.setDefaultVoiceSubId(subId);
+            if (hasMessaging()) mSubscriptionManagerService.setDefaultSmsSubId(subId);
             sendDefaultSubConfirmedNotification(subId);
             return;
         }
 
         if (DBG) log("updateDefaultValues: records: " + mPrimarySubList);
 
-        boolean dataSelected, voiceSelected, smsSelected;
+        boolean dataSelected = false;
+        boolean voiceSelected = false;
+        boolean smsSelected = false;
 
-        // Update default data subscription.
-        if (DBG) log("updateDefaultValues: Update default data subscription");
-        dataSelected = updateDefaultValue(mPrimarySubList,
-                mSubscriptionManagerService.getDefaultDataSubId(),
-                mSubscriptionManagerService::setDefaultDataSubId);
+        if (hasData()) {
+            // Update default data subscription.
+            if (DBG) log("updateDefaultValues: Update default data subscription");
+            dataSelected = updateDefaultValue(mPrimarySubList,
+                    mSubscriptionManagerService.getDefaultDataSubId(),
+                    mSubscriptionManagerService::setDefaultDataSubId);
+        }
 
-        // Update default voice subscription.
-        if (DBG) log("updateDefaultValues: Update default voice subscription");
-        voiceSelected = updateDefaultValue(mPrimarySubList,
-                mSubscriptionManagerService.getDefaultVoiceSubId(),
-                mSubscriptionManagerService::setDefaultVoiceSubId);
+        if (hasCalling()) {
+            // Update default voice subscription.
+            if (DBG) log("updateDefaultValues: Update default voice subscription");
+            voiceSelected = updateDefaultValue(mPrimarySubList,
+                    mSubscriptionManagerService.getDefaultVoiceSubId(),
+                    mSubscriptionManagerService::setDefaultVoiceSubId);
+        }
 
-        // Update default sms subscription.
-        if (DBG) log("updateDefaultValues: Update default sms subscription");
-        smsSelected = updateDefaultValue(mPrimarySubList,
-                mSubscriptionManagerService.getDefaultSmsSubId(),
-                mSubscriptionManagerService::setDefaultSmsSubId,
-                mIsAskEverytimeSupportedForSms);
+        if (hasMessaging()) {
+            // Update default sms subscription.
+            if (DBG) log("updateDefaultValues: Update default sms subscription");
+            smsSelected = updateDefaultValue(mPrimarySubList,
+                    mSubscriptionManagerService.getDefaultSmsSubId(),
+                    mSubscriptionManagerService::setDefaultSmsSubId,
+                    mIsAskEverytimeSupportedForSms);
+        }
 
         boolean autoFallbackEnabled = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_voice_data_sms_auto_fallback);
@@ -1032,11 +1054,11 @@
 
         int autoDefaultSubId = primarySubList.get(0);
 
-        if ((primarySubList.size() == 1) && !smsSelected) {
+        if (hasMessaging() && (primarySubList.size() == 1) && !smsSelected) {
             mSubscriptionManagerService.setDefaultSmsSubId(autoDefaultSubId);
         }
 
-        if ((primarySubList.size() == 1) && !voiceSelected) {
+        if (hasCalling() && (primarySubList.size() == 1) && !voiceSelected) {
             mSubscriptionManagerService.setDefaultVoiceSubId(autoDefaultSubId);
         }
 
@@ -1045,13 +1067,15 @@
         log("User pref subId = " + userPrefDataSubId + " current dds " + defaultDataSubId
                 + " next active subId " + autoDefaultSubId);
 
-        // If earlier user selected DDS is now available, set that as DDS subId.
-        if (primarySubList.contains(userPrefDataSubId)
-                && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId)
-                && (defaultDataSubId != userPrefDataSubId)) {
-            mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId);
-        } else if (!dataSelected) {
-            mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId);
+        if (hasData()) {
+            // If earlier user selected DDS is now available, set that as DDS subId.
+            if (primarySubList.contains(userPrefDataSubId)
+                    && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId)
+                    && (defaultDataSubId != userPrefDataSubId)) {
+                mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId);
+            } else if (!dataSelected) {
+                mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId);
+            }
         }
 
         if (DBG) {
diff --git a/src/java/com/android/internal/telephony/NetworkIndication.java b/src/java/com/android/internal/telephony/NetworkIndication.java
index d86c090..5c49234 100644
--- a/src/java/com/android/internal/telephony/NetworkIndication.java
+++ b/src/java/com/android/internal/telephony/NetworkIndication.java
@@ -45,10 +45,11 @@
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.CellularIdentifierDisclosure;
-import android.telephony.EmergencyRegResult;
+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;
@@ -414,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);
@@ -456,6 +457,12 @@
         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
diff --git a/src/java/com/android/internal/telephony/NetworkResponse.java b/src/java/com/android/internal/telephony/NetworkResponse.java
index eb2cd16..b4a37b3 100644
--- a/src/java/com/android/internal/telephony/NetworkResponse.java
+++ b/src/java/com/android/internal/telephony/NetworkResponse.java
@@ -24,7 +24,7 @@
 import android.os.AsyncResult;
 import android.telephony.BarringInfo;
 import android.telephony.CellInfo;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 import android.telephony.RadioAccessSpecifier;
 import android.telephony.SignalStrength;
 
@@ -429,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);
             }
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index e6fb84e..67ca1e1 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -26,6 +26,7 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation;
 import android.telephony.CarrierConfigManager;
@@ -37,10 +38,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 +79,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 +87,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 +116,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 +135,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,13 +165,42 @@
                 }
             };
 
+    @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;
     private boolean mIsPhysicalChannelConfigOn;
     private boolean mIsPrimaryTimerActive;
     private boolean mIsSecondaryTimerActive;
+    private long mSecondaryTimerExpireTimestamp;
     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 +208,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,28 +218,33 @@
     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;
+    /** Used to track link status to be DORMANT or ACTIVE */
     @Nullable private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
 
     // 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 +256,7 @@
         addState(mLegacyState, defaultState);
         addState(mIdleState, defaultState);
         addState(mLteConnectedState, defaultState);
+        addState(mNrIdleState, defaultState);
         addState(mNrConnectedState, defaultState);
         addState(mNrConnectedAdvancedState, defaultState);
         setInitialState(defaultState);
@@ -262,6 +309,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 +323,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 +346,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 +368,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 +379,11 @@
         String overrideSecondaryTimerRule = config.getString(
                 CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
         createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
-        updatePhysicalChannelConfigs();
+        updatePhysicalChannelConfigs(
+                mPhone.getServiceStateTracker().getPhysicalChannelConfigList());
+        if (isUsingPhysicalChannelConfigForRrcDetection()) {
+            mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
+        }
     }
 
     private void createTimerRules(String icons, String timers, String secondaryTimers) {
@@ -370,6 +396,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 +426,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 +437,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 +461,9 @@
                     }
                     continue;
                 }
+                if (kv[0].equals(STATE_CONNECTED_RRC_IDLE) && !mFeatureFlags.supportNrSaRrcIdle()) {
+                    continue;
+                }
                 int duration;
                 try {
                     duration = Integer.parseInt(kv[2]);
@@ -433,6 +472,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 +486,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 +615,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 +636,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);
                     }
@@ -610,13 +669,20 @@
                 case EVENT_SECONDARY_TIMER_EXPIRED:
                     if (DBG) log("Secondary timer expired for state: " + mSecondaryTimerState);
                     mIsSecondaryTimerActive = false;
+                    mSecondaryTimerExpireTimestamp = 0;
                     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 +691,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 +710,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 +762,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 +773,9 @@
                         if (isNrAdvanced()) {
                             transitionTo(mNrConnectedAdvancedState);
                         } else {
-                            transitionTo(mNrConnectedState);
+                            transitionTo(isPhysicalLinkActive()
+                                    || !mFeatureFlags.supportNrSaRrcIdle()
+                                    ? mNrConnectedState : mNrIdleState);
                         }
                     } else if (isLte(rat) && isNrNotRestricted()) {
                         transitionWithTimerTo(isPhysicalLinkActive()
@@ -703,18 +790,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 +844,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 +856,9 @@
                         if (isNrAdvanced()) {
                             transitionTo(mNrConnectedAdvancedState);
                         } else {
-                            transitionTo(mNrConnectedState);
+                            transitionTo(isPhysicalLinkActive()
+                                    || !mFeatureFlags.supportNrSaRrcIdle()
+                                    ? mNrConnectedState : mNrIdleState);
                         }
                     } else if (!isLte(rat) || !isNrNotRestricted()) {
                         transitionWithTimerTo(mLegacyState);
@@ -782,7 +872,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 +885,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 +928,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 +940,9 @@
                         if (isNrAdvanced()) {
                             transitionTo(mNrConnectedAdvancedState);
                         } else {
-                            transitionTo(mNrConnectedState);
+                            transitionTo(isPhysicalLinkActive()
+                                    || !mFeatureFlags.supportNrSaRrcIdle()
+                                    ? mNrConnectedState : mNrIdleState);
                         }
                     } else if (!isLte(rat) || !isNrNotRestricted()) {
                         transitionWithTimerTo(mLegacyState);
@@ -864,7 +956,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 +969,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 +995,88 @@
     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();
+                    }
+                    if (isPhysicalLinkActive()) {
+                        if (isNrAdvanced()) {
+                            transitionTo(mNrConnectedAdvancedState);
+                        } else {
+                            transitionWithTimerTo(mNrConnectedState);
+                        }
+                    } else {
+                        // Update in case the override network type changed
+                        updateOverrideNetworkType();
+                    }
+                    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 +1094,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 +1105,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 +1119,22 @@
                     }
                     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()) {
+                    if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
+                        transitionWithTimerTo(mNrIdleState);
+                    } else if (isNrAdvanced()) {
                         transitionTo(mNrConnectedAdvancedState);
                     }
                     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 +1170,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 +1196,9 @@
                                 mOverrideNetworkType =
                                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
                             }
-                            transitionWithTimerTo(mNrConnectedState);
+                            transitionWithTimerTo(isPhysicalLinkActive()
+                                    || !mFeatureFlags.supportNrSaRrcIdle()
+                                    ? mNrConnectedState : mNrIdleState);
                         }
                     } else if (isLte(rat) && isNrNotRestricted()) {
                         transitionWithTimerTo(isPhysicalLinkActive()
@@ -1022,18 +1208,19 @@
                     }
                     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()) {
+                    if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
+                        transitionWithTimerTo(mNrIdleState);
+                    } else if (!isNrAdvanced()) {
                         transitionWithTimerTo(mNrConnectedState);
                     }
                     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 +1240,28 @@
     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()) {
+            // Clear mPrimaryCellChangedWhileIdle to allow later potential one-off PCI change.
+            // Update link status to be DORMANT, but keep ratcheted bands.
             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 +1308,17 @@
             mRatchetedNrBandwidths = Math.max(mRatchetedNrBandwidths, nrBandwidths);
             mRatchetedNrBands.addAll(nrBands);
         } else {
+            if (mFeatureFlags.supportNrSaRrcIdle() && mDoesPccListIndicateIdle
+                    && isUsingPhysicalChannelConfigForRrcDetection()
+                    && !mPrimaryCellChangedWhileIdle
+                    && !isNrAdvancedForPccFields(nrBandwidths, nrBands)) {
+                log("Allow primary cell change once during RRC idle without changing state: "
+                        + mLastAnchorNrCellId + " -> " + anchorNrCellId);
+                mPrimaryCellChangedWhileIdle = true;
+                mLastAnchorNrCellId = anchorNrCellId;
+                reduceSecondaryTimerIfNeeded();
+                return;
+            }
             if (mRatchetPccFieldsForSameAnchorNrCell) {
                 log("Not ratcheting physical channel config fields since anchor NR cell changed: "
                         + mLastAnchorNrCellId + " -> " + anchorNrCellId);
@@ -1117,6 +1329,7 @@
 
         mLastAnchorNrCellId = anchorNrCellId;
         mPhysicalChannelConfigs = physicalChannelConfigs;
+        mDoesPccListIndicateIdle = false;
         if (DBG) {
             log("Physical channel configs updated: anchorNrCell=" + mLastAnchorNrCellId
                     + ", nrBandwidths=" + mRatchetedNrBandwidths + ", nrBands=" +  mRatchetedNrBands
@@ -1124,17 +1337,45 @@
         }
     }
 
+    /**
+     * Called when PCI change, specifically during idle state.
+     */
+    private void reduceSecondaryTimerIfNeeded() {
+        if (!mIsSecondaryTimerActive || mNrAdvancedBandsSecondaryTimer <= 0) return;
+        // Secondary timer is active, so we must have a valid secondary rule right now.
+        OverrideTimerRule secondaryRule = mOverrideTimerRules.get(mPrimaryTimerState);
+        if (secondaryRule != null) {
+            int secondaryDuration = secondaryRule.getSecondaryTimer(mSecondaryTimerState);
+            long durationMillis = secondaryDuration * 1000L;
+            if ((mSecondaryTimerExpireTimestamp - SystemClock.uptimeMillis()) > durationMillis) {
+                if (DBG) log("Due to PCI change, reduce the secondary timer to " + durationMillis);
+                removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
+                sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, mSecondaryTimerState,
+                        durationMillis);
+            }
+        } else {
+            loge("!! Secondary timer is active, but found no rule for " + mPrimaryTimerState);
+        }
+    }
+
     private void transitionWithTimerTo(IState destState) {
         String destName = destState.getName();
-        if (DBG) log("Transition with primary timer from " + mPreviousState + " to " + destName);
-        OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState);
-        if (!mIsDeviceIdleMode && rule != null && rule.getTimer(destName) > 0) {
-            int duration = rule.getTimer(destName);
-            if (DBG) log(duration + "s primary timer started for state: " + mPreviousState);
-            mPrimaryTimerState = mPreviousState;
-            mPreviousState = getCurrentState().getName();
-            mIsPrimaryTimerActive = true;
-            sendMessageDelayed(EVENT_PRIMARY_TIMER_EXPIRED, destState, duration * 1000L);
+        if (mIsPrimaryTimerActive) {
+            log("Transition without timer from " + getCurrentState().getName() + " to " + destName
+                    + " due to existing " + mPrimaryTimerState + " primary timer.");
+        } else {
+            if (DBG) {
+                log("Transition with primary timer from " + mPreviousState + " to " + destName);
+            }
+            OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState);
+            if (!mIsDeviceIdleMode && rule != null && rule.getTimer(destName) > 0) {
+                int duration = rule.getTimer(destName);
+                if (DBG) log(duration + "s primary timer started for state: " + mPreviousState);
+                mPrimaryTimerState = mPreviousState;
+                mPreviousState = getCurrentState().getName();
+                mIsPrimaryTimerActive = true;
+                sendMessageDelayed(EVENT_PRIMARY_TIMER_EXPIRED, destState, duration * 1000L);
+            }
         }
         transitionTo(destState);
     }
@@ -1148,11 +1389,17 @@
         }
         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;
             mIsSecondaryTimerActive = true;
-            sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, destState, duration * 1000L);
+            long durationMillis = duration * 1000L;
+            mSecondaryTimerExpireTimestamp = SystemClock.uptimeMillis() + durationMillis;
+            sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, destState, durationMillis);
         }
         mIsPrimaryTimerActive = false;
         transitionTo(getCurrentState());
@@ -1162,7 +1409,9 @@
         int dataRat = getDataNetworkType();
         IState transitionState;
         if (dataRat == TelephonyManager.NETWORK_TYPE_NR || (isLte(dataRat) && isNrConnected())) {
-            if (isNrAdvanced()) {
+            if (!isPhysicalLinkActive() && mFeatureFlags.supportNrSaRrcIdle()) {
+                transitionState = mNrIdleState;
+            } else if (isNrAdvanced()) {
                 transitionState = mNrConnectedAdvancedState;
             } else {
                 transitionState = mNrConnectedState;
@@ -1194,11 +1443,11 @@
 
         String currentState = getCurrentState().getName();
 
-        if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType()) {
-            // remove primary timer if device goes back to the original icon
+        if (mIsPrimaryTimerActive && mPrimaryTimerState.equals(currentState)) {
+            // remove primary timer if device goes back to the original state
             if (DBG) {
-                log("Remove primary timer since icon of primary state and current icon equal: "
-                        + mPrimaryTimerState);
+                log("Remove primary timer since primary timer state ("
+                        + mPrimaryTimerState + ") was reestablished.");
             }
             removeMessages(EVENT_PRIMARY_TIMER_EXPIRED);
             mIsPrimaryTimerActive = false;
@@ -1215,6 +1464,7 @@
             }
             removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
             mIsSecondaryTimerActive = false;
+            mSecondaryTimerExpireTimestamp = 0;
             mSecondaryTimerState = "";
             transitionToCurrentState();
             return;
@@ -1224,10 +1474,11 @@
             if (currentState.equals(STATE_CONNECTED_NR_ADVANCED)) {
                 if (DBG) log("Reset timers since state is NR_ADVANCED.");
                 resetAllTimers();
-            } else if (currentState.equals(STATE_CONNECTED)
+            } else if ((currentState.equals(STATE_CONNECTED)
+                    || currentState.equals(STATE_CONNECTED_RRC_IDLE))
                     && !mPrimaryTimerState.equals(STATE_CONNECTED_NR_ADVANCED)
                     && !mSecondaryTimerState.equals(STATE_CONNECTED_NR_ADVANCED)) {
-                if (DBG) log("Reset non-NR_ADVANCED timers since state is NR_CONNECTED");
+                if (DBG) log("Reset non-NR advanced timers since state is NR connected/idle");
                 resetAllTimers();
             } else {
                 int rat = getDataNetworkType();
@@ -1244,8 +1495,11 @@
         removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
         mIsPrimaryTimerActive = false;
         mIsSecondaryTimerActive = false;
+        mSecondaryTimerExpireTimestamp = 0;
         mPrimaryTimerState = "";
         mSecondaryTimerState = "";
+
+        mLastShownNrDueToAdvancedBand = false;
     }
 
     /**
@@ -1320,6 +1574,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 +1610,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 +1630,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 +1640,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 +1671,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 +1717,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 +1734,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 +1743,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 d944738..c088406 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
+import static android.telephony.ims.ImsService.CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -25,6 +26,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;
@@ -256,7 +258,9 @@
     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_LAST = EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE;
+    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_";
@@ -290,6 +294,9 @@
     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;
 
     /**
@@ -1078,9 +1085,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();
+                }
+            }
         }
     }
 
@@ -3749,7 +3767,7 @@
      * @return {@code true} if internet data is allowed to be established.
      */
     public boolean isDataAllowed() {
-        return getDataNetworkController().isInternetDataAllowed();
+        return getDataNetworkController().isInternetDataAllowed(false/* ignoreExistingNetworks */);
     }
 
     /**
@@ -4603,6 +4621,20 @@
         }
     }
 
+    public boolean isImsServiceSimultaneousCallingSupportCapable(Context context) {
+        if (!mFeatureFlags.simultaneousCallingIndications()) return false;
+        boolean capable = false;
+        ImsManager imsManager = ImsManager.getInstance(context, mPhoneId);
+        if (imsManager != null) {
+            try {
+                capable = imsManager.isCapable(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING);
+            } catch (ImsException e) {
+                loge("initializeTerminalBasedCallWaiting : exception " + e);
+            }
+        }
+        return capable;
+    }
+
     public void startRingbackTone() {
     }
 
@@ -5175,6 +5207,34 @@
     }
 
     /**
+     * @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}.
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 78d1387..ef96d89 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -34,6 +34,7 @@
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -43,8 +44,14 @@
 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.concurrent.CopyOnWriteArraySet;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * This class manages phone's configuration which defines the potential capability (static) of the
@@ -65,10 +72,36 @@
     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;
+
+    /**
+     * Listener interface for events related to the {@link PhoneConfigurationManager} which should
+     * be reported to the {@link SimultaneousCallingTracker}.
+     */
+    public interface Listener {
+        public void onPhoneCapabilityChanged();
+        public void onDeviceConfigChanged();
+    }
+
+    /**
+     * Base listener implementation.
+     */
+    public abstract static class ListenerBase implements Listener {
+        @Override
+        public void onPhoneCapabilityChanged() {}
+        @Override
+        public void onDeviceConfigChanged() {}
+    }
+
 
     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
@@ -81,6 +114,9 @@
     /** Feature flags */
     @NonNull
     private final FeatureFlags mFeatureFlags;
+    private final DefaultPhoneNotifier mNotifier;
+    public Set<Listener> mListeners = new CopyOnWriteArraySet<>();
+
     /**
      * True if 'Virtual DSDA' i.e., in-call IMS connectivity on both subs with only single logical
      * modem, is enabled.
@@ -122,6 +158,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 -> {
@@ -140,6 +177,43 @@
         }
     }
 
+    /**
+     * Assign a listener to be notified of state changes.
+     *
+     * @param listener A listener.
+     */
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener.
+     *
+     * @param listener A listener.
+     */
+    public final void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * 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);
     }
@@ -152,18 +226,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.
      */
@@ -223,6 +345,10 @@
                     if (ar != null && ar.exception == null) {
                         mStaticCapability = (PhoneCapability) ar.result;
                         notifyCapabilityChanged();
+                        for (Listener l : mListeners) {
+                            l.onPhoneCapabilityChanged();
+                        }
+                        maybeEnableCellularDSDASupport();
                     } else {
                         log(msg.what + " failure. Not getting phone capability." + ar.exception);
                     }
@@ -234,6 +360,48 @@
                         log("EVENT_DEVICE_CONFIG_CHANGED: from " + mVirtualDsdaEnabled + " to "
                                 + isVirtualDsdaEnabled);
                         mVirtualDsdaEnabled = isVirtualDsdaEnabled;
+                        for (Listener l : mListeners) {
+                            l.onDeviceConfigChanged();
+                        }
+                    }
+                    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:
@@ -342,6 +510,30 @@
     }
 
     /**
+     * @return The updated list of logical slots that support simultaneous cellular calling from the
+     * modem based on current network conditions.
+     */
+    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.
      */
     public synchronized PhoneCapability getStaticPhoneCapability() {
@@ -351,7 +543,7 @@
                     mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
             mRadioConfig.getPhoneCapability(callback);
         }
-        mStaticCapability = maybeUpdateMaxActiveVoiceSubscriptions(mStaticCapability);
+        mStaticCapability = maybeOverrideMaxActiveVoiceSubscriptions(mStaticCapability);
         log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability);
         return mStaticCapability;
     }
@@ -367,10 +559,40 @@
         return mStaticCapability.getMaxActiveDataSubscriptions();
     }
 
-    private void notifyCapabilityChanged() {
-        PhoneNotifier notifier = new DefaultPhoneNotifier(mContext, mFeatureFlags);
+    public int getNumberOfModemsWithSimultaneousVoiceConnections() {
+        return maybeOverrideMaxActiveVoiceSubscriptions(mStaticCapability)
+                .getMaxActiveVoiceSubscriptions();
+    }
 
-        notifier.notifyPhoneCapabilityChanged(mStaticCapability);
+    public boolean isVirtualDsdaEnabled() {
+        return mVirtualDsdaEnabled;
+    }
+
+    /**
+     * 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);
+    }
+
+    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));
     }
 
     /**
@@ -446,6 +668,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)
@@ -621,6 +857,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 d29eed1..803fb19 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -98,6 +98,7 @@
     @UnsupportedAppUsage
     static private Context sContext;
     static private PhoneConfigurationManager sPhoneConfigurationManager;
+    static private SimultaneousCallingTracker sSimultaneousCallingTracker;
     static private PhoneSwitcher sPhoneSwitcher;
     static private TelephonyNetworkFactory[] sTelephonyNetworkFactories;
     static private NotificationChannelController sNotificationChannelController;
@@ -211,12 +212,12 @@
                         Looper.myLooper(), featureFlags);
 
                 TelephonyComponentFactory.getInstance().inject(MultiSimSettingController.class.
-                        getName()).initMultiSimSettingController(context);
+                        getName()).initMultiSimSettingController(context, featureFlags);
 
                 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++) {
@@ -257,6 +258,10 @@
                 }
 
                 sPhoneConfigurationManager = PhoneConfigurationManager.init(sContext, featureFlags);
+                if (featureFlags.simultaneousCallingIndications()) {
+                    sSimultaneousCallingTracker =
+                            SimultaneousCallingTracker.init(sContext, featureFlags);
+                }
 
                 sCellularNetworkValidator = CellularNetworkValidator.make(sContext);
 
@@ -268,7 +273,7 @@
                         makePhoneSwitcher(maxActivePhones, sContext, Looper.myLooper(),
                                 featureFlags);
 
-                sProxyController = ProxyController.getInstance(context);
+                sProxyController = ProxyController.getInstance(context, featureFlags);
 
                 sIntentBroadcaster = IntentBroadcaster.getInstance(context);
 
@@ -276,7 +281,7 @@
 
                 for (int i = 0; i < numPhones; i++) {
                     sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                            Looper.myLooper(), sPhones[i]);
+                            Looper.myLooper(), sPhones[i], featureFlags);
                 }
             }
         }
@@ -285,8 +290,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) {
@@ -313,7 +319,7 @@
                     sPhones[i].createImsPhone();
                 }
                 sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                        Looper.myLooper(), sPhones[i]);
+                        Looper.myLooper(), sPhones[i], sFeatureFlags);
             }
         }
     }
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 5177adb..8b3be1e 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -31,6 +31,7 @@
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.radio.V1_0.IRadio;
 import android.hardware.radio.V1_0.RadioError;
 import android.hardware.radio.V1_0.RadioIndicationType;
@@ -271,6 +272,13 @@
 
     static final String[] HIDL_SERVICE_NAME = {"slot1", "slot2", "slot3"};
 
+    private static final Map<String, Integer> FEATURES_TO_SERVICES = Map.ofEntries(
+            Map.entry(PackageManager.FEATURE_TELEPHONY_CALLING, HAL_SERVICE_VOICE),
+            Map.entry(PackageManager.FEATURE_TELEPHONY_DATA, HAL_SERVICE_DATA),
+            Map.entry(PackageManager.FEATURE_TELEPHONY_MESSAGING, HAL_SERVICE_MESSAGING),
+            Map.entry(PackageManager.FEATURE_TELEPHONY_IMS, HAL_SERVICE_IMS)
+    );
+
     public static List<TelephonyHistogram> getTelephonyRILTimingHistograms() {
         List<TelephonyHistogram> list;
         synchronized (sRilTimeHistograms) {
@@ -621,6 +629,7 @@
             if (mDisabledRadioServices.get(HAL_SERVICE_RADIO).contains(mPhoneId)) {
                 riljLoge("getRadioProxy: mRadioProxy for " + HIDL_SERVICE_NAME[mPhoneId]
                         + " is disabled");
+                return null;
             } else {
                 try {
                     mRadioProxy = android.hardware.radio.V1_6.IRadio.getService(
@@ -662,6 +671,7 @@
                     mDisabledRadioServices.get(HAL_SERVICE_RADIO).add(mPhoneId);
                     riljLoge("getRadioProxy: set mRadioProxy for "
                             + HIDL_SERVICE_NAME[mPhoneId] + " as disabled");
+                    return null;
                 }
             }
         } catch (RemoteException e) {
@@ -718,6 +728,9 @@
     public synchronized RadioServiceProxy getRadioServiceProxy(int service) {
         if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return mServiceProxies.get(service);
         if ((service >= HAL_SERVICE_IMS) && !isRadioServiceSupported(service)) {
+            riljLogw("getRadioServiceProxy: " + serviceToString(service) + " for "
+                    + HIDL_SERVICE_NAME[mPhoneId] + " is not supported\n"
+                    + android.util.Log.getStackTraceString(new RuntimeException()));
             return mServiceProxies.get(service);
         }
         if (!mIsCellularSupported) {
@@ -733,7 +746,9 @@
         try {
             if (mMockModem == null && mDisabledRadioServices.get(service).contains(mPhoneId)) {
                 riljLoge("getRadioServiceProxy: " + serviceToString(service) + " for "
-                        + HIDL_SERVICE_NAME[mPhoneId] + " is disabled");
+                        + HIDL_SERVICE_NAME[mPhoneId] + " is disabled\n"
+                        + android.util.Log.getStackTraceString(new RuntimeException()));
+                return null;
             } else {
                 IBinder binder;
                 switch (service) {
@@ -944,7 +959,8 @@
                     mDisabledRadioServices.get(service).add(mPhoneId);
                     mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN);
                     riljLoge("getRadioServiceProxy: set " + serviceToString(service) + " for "
-                            + HIDL_SERVICE_NAME[mPhoneId] + " as disabled");
+                            + HIDL_SERVICE_NAME[mPhoneId] + " as disabled\n"
+                            + android.util.Log.getStackTraceString(new RuntimeException()));
                 }
             }
         } catch (RemoteException e) {
@@ -1094,9 +1110,16 @@
             tdc.registerRIL(this);
         }
 
+        validateFeatureFlags();
+
         // Set radio callback; needed to set RadioIndication callback (should be done after
         // wakelock stuff is initialized above as callbacks are received on separate binder threads)
         for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
+            if (!isRadioServiceSupported(service)) {
+                riljLog("Not initializing " + serviceToString(service) + " (not supported)");
+                continue;
+            }
+
             if (service == HAL_SERVICE_RADIO) {
                 getRadioProxy();
             } else {
@@ -1161,6 +1184,26 @@
         return false;
     }
 
+    private void validateFeatureFlags() {
+        PackageManager pm = mContext.getPackageManager();
+        for (var entry : FEATURES_TO_SERVICES.entrySet()) {
+            String feature = entry.getKey();
+            int service = entry.getValue();
+
+            boolean hasFeature = pm.hasSystemFeature(feature);
+            boolean hasService = isRadioServiceSupported(service);
+
+            if (hasFeature && !hasService) {
+                riljLoge("Feature " + feature + " is declared, but service "
+                        + serviceToString(service) + " is missing");
+            }
+            if (!hasFeature && hasService) {
+                riljLoge("Service " + serviceToString(service) + " is available, but feature "
+                        + feature + " is not declared");
+            }
+        }
+    }
+
     private boolean isRadioBugDetectionEnabled() {
         return Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.ENABLE_RADIO_BUG_DETECTION, 1) != 0;
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index 94f2e96..8897db4 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -98,6 +98,7 @@
 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;
@@ -116,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;
@@ -157,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;
@@ -176,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;
@@ -231,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;
@@ -272,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;
@@ -300,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;
@@ -326,7 +335,7 @@
 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;
@@ -334,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;
@@ -4030,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;
@@ -4051,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
@@ -4381,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));
@@ -4397,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);
     }
@@ -4449,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,
@@ -5128,6 +5225,16 @@
                 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 + ">";
         }
@@ -5250,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";
@@ -5273,6 +5384,8 @@
                 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 + ">";
         }
@@ -5690,6 +5803,20 @@
                 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 6bf0203..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;
@@ -79,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();
@@ -478,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) {
@@ -566,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 9d05fc5..b6c6d68 100644
--- a/src/java/com/android/internal/telephony/RadioConfigProxy.java
+++ b/src/java/com/android/internal/telephony/RadioConfigProxy.java
@@ -225,6 +225,16 @@
     }
 
     /**
+     * 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.
      */
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/RadioInterfaceCapabilityController.java b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
index bab4d12..4d9196e 100644
--- a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
+++ b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
@@ -86,6 +86,13 @@
         register();
     }
 
+    private void requestCapabilities() {
+        if (mRadioInterfaceCapabilities != null) return;
+
+        mRadioConfig.getHalDeviceCapabilities(obtainMessage(
+                EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE));
+    }
+
     /**
      * Gets the radio interface capabilities for the device
      */
@@ -95,8 +102,7 @@
             // Only incur cost of synchronization block if mRadioInterfaceCapabilities isn't null
             synchronized (mLockRadioInterfaceCapabilities) {
                 if (mRadioInterfaceCapabilities == null) {
-                    mRadioConfig.getHalDeviceCapabilities(
-                            obtainMessage(EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE));
+                    requestCapabilities();
                     try {
                         if (Looper.myLooper() != getLooper()) {
                             mLockRadioInterfaceCapabilities.wait(2000);
@@ -158,7 +164,7 @@
         switch (msg.what) {
             case Phone.EVENT_RADIO_AVAILABLE:
             case Phone.EVENT_RADIO_ON:
-                getCapabilities();
+                requestCapabilities();
                 break;
             case EVENT_GET_HAL_DEVICE_CAPABILITIES_DONE:
                 setupCapabilities((AsyncResult) msg.obj);
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 04f5c08..498535b 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -1013,10 +1013,12 @@
      * Notifies the {@link SmsDispatchersController} that sending MO SMS is failed.
      *
      * @param tracker holds the SMS message to be sent
+     * @param isOverIms a flag specifying whether SMS is sent via IMS or not
      */
-    protected void notifySmsSentFailedToEmergencyStateTracker(SmsTracker tracker) {
+    protected void notifySmsSentFailedToEmergencyStateTracker(SmsTracker tracker,
+            boolean isOverIms) {
         mSmsDispatchersController.notifySmsSentFailedToEmergencyStateTracker(
-                tracker.mDestAddress, tracker.mMessageId);
+                tracker.mDestAddress, tracker.mMessageId, isOverIms);
     }
 
     /**
@@ -1052,7 +1054,7 @@
             tracker.onSent(mContext);
             mPhone.notifySmsSent(tracker.mDestAddress);
             mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
-                    tracker.mDestAddress, tracker.mMessageId);
+                    tracker.mDestAddress, tracker.mMessageId, false);
 
             mPhone.getSmsStats().onOutgoingSms(
                     tracker.mImsRetry > 0 /* isOverIms */,
@@ -1103,7 +1105,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);
+                notifySmsSentFailedToEmergencyStateTracker(tracker, false);
                 mPhone.getSmsStats().onOutgoingSms(
                         tracker.mImsRetry > 0 /* isOverIms */,
                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
@@ -1164,7 +1166,7 @@
             } else {
                 int errorCode = (smsResponse != null) ? smsResponse.mErrorCode : NO_ERROR_CODE;
                 tracker.onFailed(mContext, error, errorCode);
-                notifySmsSentFailedToEmergencyStateTracker(tracker);
+                notifySmsSentFailedToEmergencyStateTracker(tracker, false);
                 mPhone.getSmsStats().onOutgoingSms(
                         tracker.mImsRetry > 0 /* isOverIms */,
                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
@@ -2389,7 +2391,7 @@
             int errorCode) {
         for (SmsTracker tracker : trackers) {
             tracker.onFailed(mContext, error, errorCode);
-            notifySmsSentFailedToEmergencyStateTracker(tracker);
+            notifySmsSentFailedToEmergencyStateTracker(tracker, false);
         }
         if (trackers.length > 0) {
             // This error occurs before the SMS is sent. Make an assumption if it would have
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index d19e4cb..ba6479e 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -681,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);
@@ -739,7 +739,7 @@
 
         mAccessNetworksManagerCallback = new AccessNetworksManagerCallback(this::post) {
             @Override
-            public void onPreferredTransportChanged(int networkCapability) {
+            public void onPreferredTransportChanged(int networkCapability, boolean forceReconnect) {
                 // Check if preferred on IWLAN was changed in ServiceState.
                 boolean isIwlanPreferred = mAccessNetworksManager.isAnyApnOnIwlan();
                 if (mSS.isIwlanPreferred() != isIwlanPreferred) {
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/SimultaneousCallingTracker.java b/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java
new file mode 100644
index 0000000..0a14ccd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java
@@ -0,0 +1,517 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Message;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.stream.Collectors;
+
+public class SimultaneousCallingTracker {
+    private static SimultaneousCallingTracker sInstance = null;
+    private final Context mContext;
+
+    /**
+     * A dynamic map of all voice capable {@link Phone} objects mapped to the set of {@link Phone}
+     * objects each {@link Phone} has a compatible user association with. To be considered
+     * compatible based on user association, both must be associated with the same
+     * {@link android.os.UserHandle} or both must be unassociated.
+     */
+    private Map<Phone, Set<Phone>> mVoiceCapablePhoneMap = new HashMap<>();
+
+    @VisibleForTesting
+    public boolean isDeviceSimultaneousCallingCapable = false;
+    public Set<Listener> mListeners = new CopyOnWriteArraySet<>();
+    private final PhoneConfigurationManager mPhoneConfigurationManager;
+    private final Handler mHandler;
+
+    /**
+     * A dynamic map of all the Phone IDs mapped to the set of {@link Phone} objects each
+     * {@link Phone} supports simultaneous calling (DSDA) with.
+     */
+    private Map<Integer, Set<Phone>> mSimultaneousCallPhoneSupportMap = new HashMap<>();
+    private static final String LOG_TAG = "SimultaneousCallingTracker";
+    protected static final int EVENT_SUBSCRIPTION_CHANGED         = 101;
+    protected static final int EVENT_PHONE_CAPABILITY_CHANGED     = 102;
+    protected static final int EVENT_MULTI_SIM_CONFIG_CHANGED     = 103;
+    protected static final int EVENT_DEVICE_CONFIG_CHANGED        = 104;
+    protected static final int EVENT_IMS_REGISTRATION_CHANGED     = 105;
+
+    /** Feature flags */
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
+    /**
+     * Init method to instantiate the object
+     * Should only be called once.
+     */
+    public static SimultaneousCallingTracker init(Context context,
+            @NonNull FeatureFlags featureFlags) {
+        if (sInstance == null) {
+            sInstance = new SimultaneousCallingTracker(context, featureFlags);
+        } else {
+            Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Constructor.
+     * @param context context needed to send broadcast.
+     */
+    private SimultaneousCallingTracker(Context context, @NonNull FeatureFlags featureFlags) {
+        mContext = context;
+        mFeatureFlags = featureFlags;
+        mHandler = new ConfigManagerHandler();
+        mPhoneConfigurationManager = PhoneConfigurationManager.getInstance();
+        mPhoneConfigurationManager.addListener(mPhoneConfigurationManagerListener);
+        PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
+                EVENT_MULTI_SIM_CONFIG_CHANGED, null);
+        TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
+                context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+        telephonyRegistryManager.addOnSubscriptionsChangedListener(
+                mSubscriptionsChangedListener, new HandlerExecutor(mHandler));
+    }
+
+    /**
+     * Static method to get instance.
+     */
+    public static SimultaneousCallingTracker getInstance() {
+        if (sInstance == null) {
+            Log.wtf(LOG_TAG, "getInstance null");
+        }
+
+        return sInstance;
+    }
+
+    /**
+     * Handler class to handle callbacks
+     */
+    private final class ConfigManagerHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            if (!mFeatureFlags.simultaneousCallingIndications()) { return; }
+            Log.v(LOG_TAG, "Received EVENT " + msg.what);
+            switch (msg.what) {
+                case EVENT_PHONE_CAPABILITY_CHANGED -> {
+                    checkSimultaneousCallingDeviceCapability();
+                }
+                case EVENT_SUBSCRIPTION_CHANGED -> {
+                    updatePhoneMapAndSimultaneousCallSupportMap();
+                }
+                case EVENT_MULTI_SIM_CONFIG_CHANGED -> {
+                    int activeModemCount = (int) ((AsyncResult) msg.obj).result;
+                    if (activeModemCount > 1) {
+                        // SSIM --> MSIM: recalculate simultaneous calling supported combinations
+                        updatePhoneMapAndSimultaneousCallSupportMap();
+                    } else {
+                        // MSIM --> SSIM: remove all simultaneous calling supported combinations
+                        disableSimultaneousCallingSupport();
+                        handleSimultaneousCallingSupportChanged();
+                    }
+                }
+                case EVENT_DEVICE_CONFIG_CHANGED, EVENT_IMS_REGISTRATION_CHANGED -> {
+                    updateSimultaneousCallSupportMap();
+                }
+                default -> Log.i(LOG_TAG, "Received unknown event: " + msg.what);
+            }
+        }
+    }
+
+    /**
+     * Listener interface for events related to the {@link SimultaneousCallingTracker}.
+     */
+    public interface Listener {
+        /**
+         * Inform Telecom that the simultaneous calling subscription support map may have changed.
+         *
+         * @param simultaneousCallSubSupportMap Map of all voice capable subscription IDs mapped to
+         *                                      a set containing the subscription IDs which that
+         *                                      subscription is DSDA compatible with.
+         */
+        public void onSimultaneousCallingSupportChanged(Map<Integer,
+                Set<Integer>> simultaneousCallSubSupportMap);
+    }
+
+    /**
+     * Base listener implementation.
+     */
+    public abstract static class ListenerBase implements SimultaneousCallingTracker.Listener {
+        @Override
+        public void onSimultaneousCallingSupportChanged(Map<Integer,
+                Set<Integer>> simultaneousCallSubSupportMap) {}
+    }
+
+    /**
+     * Assign a listener to be notified of state changes.
+     *
+     * @param listener A listener.
+     */
+    public void addListener(Listener listener) {
+        if (mFeatureFlags.simultaneousCallingIndications()) {
+            mListeners.add(listener);
+        }
+    }
+
+    /**
+     * Removes a listener.
+     *
+     * @param listener A listener.
+     */
+    public final void removeListener(Listener listener) {
+        if (mFeatureFlags.simultaneousCallingIndications()) {
+            mListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Listener for listening to events in the {@link android.telephony.TelephonyRegistryManager}
+     */
+    private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+                @Override
+                public void onSubscriptionsChanged() {
+                    if (!mHandler.hasMessages(EVENT_SUBSCRIPTION_CHANGED)) {
+                        mHandler.sendEmptyMessage(EVENT_SUBSCRIPTION_CHANGED);
+                    }
+                }
+            };
+
+    /**
+     * Listener for listening to events in the {@link PhoneConfigurationManager}.
+     */
+    private final PhoneConfigurationManager.Listener mPhoneConfigurationManagerListener =
+            new PhoneConfigurationManager.Listener() {
+                @Override
+                public void onPhoneCapabilityChanged() {
+                    if (!mHandler.hasMessages(EVENT_PHONE_CAPABILITY_CHANGED)) {
+                        mHandler.sendEmptyMessage(EVENT_PHONE_CAPABILITY_CHANGED);
+                    }
+                }
+                @Override
+                public void onDeviceConfigChanged() {
+                    if (!mHandler.hasMessages(EVENT_DEVICE_CONFIG_CHANGED)) {
+                        mHandler.sendEmptyMessage(EVENT_DEVICE_CONFIG_CHANGED);
+                    }
+                }
+            };
+
+    private void checkSimultaneousCallingDeviceCapability() {
+        if (mPhoneConfigurationManager.getNumberOfModemsWithSimultaneousVoiceConnections() > 1) {
+            isDeviceSimultaneousCallingCapable = true;
+            mPhoneConfigurationManager.registerForSimultaneousCellularCallingSlotsChanged(
+                    this::onSimultaneousCellularCallingSlotsChanged);
+        }
+    }
+
+    /**
+     *
+     * @param subId to get the slots supporting simultaneous calling with
+     * @return the set of subId's that support simultaneous calling with the param subId
+     */
+    public Set<Integer> getSubIdsSupportingSimultaneousCalling(int subId) {
+        if (!isDeviceSimultaneousCallingCapable) {
+            Log.v(LOG_TAG, "Device is not simultaneous calling capable");
+            return Collections.emptySet();
+        }
+        for (int phoneId : mSimultaneousCallPhoneSupportMap.keySet()) {
+            if (PhoneFactory.getPhone(phoneId).getSubId() == subId) {
+                Set<Integer> subIdsSupportingSimultaneousCalling = new HashSet<>();
+                for (Phone phone : mSimultaneousCallPhoneSupportMap.get(phoneId)) {
+                    subIdsSupportingSimultaneousCalling.add(phone.getSubId());
+                }
+                Log.d(LOG_TAG, "getSlotsSupportingSimultaneousCalling for subId=" + subId +
+                        "; subIdsSupportingSimultaneousCalling=[" +
+                        getStringFromSet(subIdsSupportingSimultaneousCalling) + "].");
+                return subIdsSupportingSimultaneousCalling;
+            }
+        }
+        Log.e(LOG_TAG, "getSlotsSupportingSimultaneousCalling: Subscription ID not found in"
+                + " the map of voice capable phones.");
+        return Collections.emptySet();
+    }
+
+    private void updatePhoneMapAndSimultaneousCallSupportMap() {
+        if (!isDeviceSimultaneousCallingCapable) {
+            Log.d(LOG_TAG, "Ignoring updatePhoneMapAndSimultaneousCallSupportMap since device "
+                    + "is not DSDA capable.");
+            return;
+        }
+        unregisterForImsRegistrationChanges(mVoiceCapablePhoneMap);
+        mVoiceCapablePhoneMap = generateVoiceCapablePhoneMapBasedOnUserAssociation();
+        Log.i(LOG_TAG, "updatePhoneMapAndSimultaneousCallSupportMap: mVoiceCapablePhoneMap.size = "
+                + mVoiceCapablePhoneMap.size());
+        registerForImsRegistrationChanges(mVoiceCapablePhoneMap);
+        updateSimultaneousCallSupportMap();
+    }
+
+    private void updateSimultaneousCallSupportMap() {
+        if (!isDeviceSimultaneousCallingCapable) {
+            Log.d(LOG_TAG, "Ignoring updateSimultaneousCallSupportMap since device is not DSDA"
+                    + "capable.");
+            return;
+        }
+        mSimultaneousCallPhoneSupportMap =
+                generateSimultaneousCallSupportMap(mVoiceCapablePhoneMap);
+        handleSimultaneousCallingSupportChanged();
+    }
+
+    /**
+     * The simultaneous cellular calling slots have changed.
+     * @param slotIds The Set of slotIds that have simultaneous cellular calling.
+     */
+    private void onSimultaneousCellularCallingSlotsChanged(Set<Integer> slotIds) {
+        //Cellular calling slots have changed - regenerate simultaneous calling support map:
+        updateSimultaneousCallSupportMap();
+    }
+
+    private void disableSimultaneousCallingSupport() {
+        if (!isDeviceSimultaneousCallingCapable) {
+            Log.d(LOG_TAG, "Ignoring updateSimultaneousCallSupportMap since device is not DSDA"
+                    + "capable.");
+            return;
+        }
+        unregisterForImsRegistrationChanges(mVoiceCapablePhoneMap);
+
+        // In Single-SIM mode, simultaneous calling is not supported at all:
+        mSimultaneousCallPhoneSupportMap.clear();
+        mVoiceCapablePhoneMap.clear();
+    }
+
+    /**
+     * Registers a listener to receive IMS registration changes for all phones in the phoneMap.
+     *
+     * @param phoneMap Map of voice capable phones mapped to the set of phones each has a compatible
+     *                 user association with.
+     */
+    private void registerForImsRegistrationChanges(Map<Phone, Set<Phone>> phoneMap) {
+        for (Phone phone : phoneMap.keySet()) {
+            ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+            if (imsPhone != null) {
+                Log.v(LOG_TAG, "registerForImsRegistrationChanges: registering phoneId = " +
+                        phone.getPhoneId());
+                imsPhone.registerForImsRegistrationChanges(mHandler,
+                        EVENT_IMS_REGISTRATION_CHANGED, null);
+            } else {
+                Log.v(LOG_TAG, "registerForImsRegistrationChanges: phone not recognized as "
+                        + "ImsPhone: phoneId = " + phone.getPhoneId());
+            }
+        }
+    }
+
+    /**
+     * Unregisters the listener to stop receiving IMS registration changes for all phones in the
+     * phoneMap.
+     *
+     * @param phoneMap Map of voice capable phones mapped to the set of phones each has a compatible
+     *                 user association with.
+     */
+    private void unregisterForImsRegistrationChanges(Map<Phone, Set<Phone>> phoneMap) {
+        for (Phone phone : phoneMap.keySet()) {
+            ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+            if (imsPhone != null) {
+                imsPhone.unregisterForImsRegistrationChanges(mHandler);
+            }
+        }
+    }
+
+    /**
+     * Generates mVoiceCapablePhoneMap by iterating through {@link PhoneFactory#getPhones()} and
+     * checking whether each {@link Phone} corresponds to a valid and voice capable subscription.
+     * Maps the voice capable phones to the other voice capable phones that have compatible user
+     * associations
+     */
+    private Map<Phone, Set<Phone>> generateVoiceCapablePhoneMapBasedOnUserAssociation() {
+        Map<Phone, Set<Phone>> voiceCapablePhoneMap = new HashMap<>(3);
+
+        // Generate a map of phone slots that corresponds to valid and voice capable subscriptions:
+        Phone[] allPhones = PhoneFactory.getPhones();
+        for (Phone phone : allPhones) {
+            int subId = phone.getSubId();
+            SubscriptionInfo subInfo =
+                    SubscriptionManagerService.getInstance().getSubscriptionInfo(subId);
+
+            if (mFeatureFlags.dataOnlyCellularService() &&
+                    subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID && subInfo != null &&
+                    subInfo.getServiceCapabilities()
+                            .contains(SubscriptionManager.SERVICE_CAPABILITY_VOICE)) {
+                Log.v(LOG_TAG, "generateVoiceCapablePhoneMapBasedOnUserAssociation: adding "
+                        + "phoneId = " + phone.getPhoneId());
+                voiceCapablePhoneMap.put(phone, new HashSet<>(3));
+            }
+        }
+
+        Map<Phone, Set<Phone>> userAssociationPhoneMap = new HashMap<>(3);
+        // Map the voice capable phones to the others that have compatible user associations:
+        for (Phone phone1 : voiceCapablePhoneMap.keySet()) {
+            Set<Phone> phone1UserAssociationCompatiblePhones = new HashSet<>(3);
+            for (Phone phone2 : voiceCapablePhoneMap.keySet()) {
+                if (phone1.getPhoneId() == phone2.getPhoneId()) { continue; }
+                if (phonesHaveSameUserAssociation(phone1, phone2)) {
+                    phone1UserAssociationCompatiblePhones.add(phone2);
+                }
+            }
+            userAssociationPhoneMap.put(phone1, phone1UserAssociationCompatiblePhones);
+        }
+
+        return userAssociationPhoneMap;
+    }
+
+    private Map<Integer, Set<Phone>> generateSimultaneousCallSupportMap(
+            Map<Phone, Set<Phone>> phoneMap) {
+        Map<Integer, Set<Phone>> simultaneousCallSubSupportMap = new HashMap<>(3);
+
+        // Initially populate simultaneousCallSubSupportMap based on the passed in phoneMap:
+        for (Phone phone : phoneMap.keySet()) {
+            simultaneousCallSubSupportMap.put(phone.getPhoneId(),
+                    new HashSet<>(phoneMap.get(phone)));
+        }
+
+        // Remove phone combinations that don't support simultaneous calling from the support map:
+        for (Phone phone : phoneMap.keySet()) {
+            if (phone.isImsRegistered()) {
+                if (mPhoneConfigurationManager.isVirtualDsdaEnabled() ||
+                        phone.isImsServiceSimultaneousCallingSupportCapable(mContext)) {
+                    // Check if the transport types of each phone support simultaneous IMS calling:
+                    int phone1TransportType = ((ImsPhone) phone.getImsPhone()).getTransportType();
+                    if (phone1TransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+                        // The transport type of this phone is WLAN so all combos are supported:
+                        continue;
+                    }
+                    for (Phone phone2 : phoneMap.keySet()) {
+                        if (phone.getPhoneId() == phone2.getPhoneId()) { continue; }
+                        if (!phonesSupportSimultaneousCallingViaCellularOrWlan(phone, phone2)) {
+                            simultaneousCallSubSupportMap.get(phone.getPhoneId()).remove(phone2);
+                        }
+                    }
+                } else {
+                    // IMS is registered, vDSDA is disabled, but IMS is not DSDA capable so
+                    // clear the map for this phone:
+                    simultaneousCallSubSupportMap.get(phone.getPhoneId()).clear();
+                }
+            } else {
+                // Check if this phone supports simultaneous cellular calling with other phones:
+                for (Phone phone2 : phoneMap.keySet()) {
+                    if (phone.getPhoneId() == phone2.getPhoneId()) { continue; }
+                    if (!phonesSupportSimultaneousCallingViaCellularOrWlan(phone, phone2)) {
+                        simultaneousCallSubSupportMap.get(phone.getPhoneId()).remove(phone2);
+                    }
+                }
+            }
+        }
+        Log.v(LOG_TAG, "generateSimultaneousCallSupportMap: returning "
+                + "simultaneousCallSubSupportMap = " +
+                getStringFromMap(simultaneousCallSubSupportMap));
+        return simultaneousCallSubSupportMap;
+    }
+
+    /**
+     * Determines whether the {@link Phone} instances have compatible user associations. To be
+     * considered compatible based on user association, both must be associated with the same
+     * {@link android.os.UserHandle} or both must be unassociated.
+     */
+    private boolean phonesHaveSameUserAssociation(Phone phone1, Phone phone2) {
+        return Objects.equals(phone1.getUserHandle(), phone2.getUserHandle());
+    }
+
+    private boolean phonesSupportCellularSimultaneousCalling(Phone phone1, Phone phone2) {
+        Set<Integer> slotsSupportingSimultaneousCellularCalls =
+                mPhoneConfigurationManager.getSlotsSupportingSimultaneousCellularCalls();
+        Log.v(LOG_TAG, "phonesSupportCellularSimultaneousCalling: modem returned slots = " +
+                getStringFromSet(slotsSupportingSimultaneousCellularCalls));
+        if (slotsSupportingSimultaneousCellularCalls.contains(phone1.getPhoneId()) &&
+                slotsSupportingSimultaneousCellularCalls.contains(phone2.getPhoneId())) {
+            return true;
+        };
+        return false;
+    }
+
+    private boolean phonesSupportSimultaneousCallingViaCellularOrWlan(Phone phone1, Phone phone2) {
+        int phone2TransportType =
+                ((ImsPhone) phone2.getImsPhone()).getTransportType();
+        return phone2TransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN ||
+                phonesSupportCellularSimultaneousCalling(phone1, phone2);
+    }
+
+    private void handleSimultaneousCallingSupportChanged() {
+        try {
+            Log.v(LOG_TAG, "handleSimultaneousCallingSupportChanged");
+            // Convert mSimultaneousCallPhoneSupportMap to a map of each subId to a set of the
+            // subIds it supports simultaneous calling with:
+            Map<Integer, Set<Integer>> simultaneousCallSubscriptionIdMap = new HashMap<>();
+            for (Integer phoneId : mSimultaneousCallPhoneSupportMap.keySet()) {
+                Phone phone = PhoneFactory.getPhone(phoneId);
+                if (phone == null) {
+                    Log.wtf(LOG_TAG, "handleSimultaneousCallingSupportChanged: phoneId=" +
+                            phoneId + " not found.");
+                    return;
+                }
+                int subId = phone.getSubId();
+                Set<Integer> supportedSubscriptionIds = new HashSet<>(3);
+                for (Phone p : mSimultaneousCallPhoneSupportMap.get(phoneId)) {
+                    supportedSubscriptionIds.add(p.getSubId());
+                }
+                simultaneousCallSubscriptionIdMap.put(subId, supportedSubscriptionIds);
+            }
+
+            // Notify listeners that simultaneous calling support has changed:
+            for (Listener l : mListeners) {
+                l.onSimultaneousCallingSupportChanged(simultaneousCallSubscriptionIdMap);
+            }
+        } catch (Exception e) {
+            Log.w(LOG_TAG, "handleVideoCapabilitiesChanged: Exception = " + e);
+        }
+    }
+
+    private String getStringFromMap(Map<Integer, Set<Phone>> phoneMap) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<Integer, Set<Phone>> entry : phoneMap.entrySet()) {
+            sb.append("Phone ID=");
+            sb.append(entry.getKey());
+            sb.append(" - Simultaneous calling compatible phone IDs=[");
+            sb.append(entry.getValue().stream().map(Phone::getPhoneId).map(String::valueOf)
+                    .collect(Collectors.joining(", ")));
+            sb.append("]; ");
+        }
+        return sb.toString();
+    }
+
+    private String getStringFromSet(Set<Integer> integerSet) {
+        return integerSet.stream().map(String::valueOf).collect(Collectors.joining(","));
+    }
+}
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 8795840..cc287f8 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -109,6 +109,9 @@
     /** Called when AP domain selection is abnormally terminated. */
     private static final int EVENT_DOMAIN_SELECTION_TERMINATED_ABNORMALLY = 20;
 
+    /** Called when MT SMS is received via IMS. */
+    private static final int EVENT_SMS_RECEIVED_VIA_IMS = 21;
+
     /** 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 */
@@ -487,8 +490,10 @@
                 String destAddr = (String) args.arg1;
                 Long messageId = (Long) args.arg2;
                 Boolean success = (Boolean) args.arg3;
+                Boolean isOverIms = (Boolean) args.arg4;
                 try {
-                    handleSmsSentCompletedUsingDomainSelection(destAddr, messageId, success);
+                    handleSmsSentCompletedUsingDomainSelection(
+                            destAddr, messageId, success, isOverIms);
                 } finally {
                     args.recycle();
                 }
@@ -499,6 +504,10 @@
                         (DomainSelectionConnectionHolder) msg.obj);
                 break;
             }
+            case EVENT_SMS_RECEIVED_VIA_IMS: {
+                handleSmsReceivedViaIms((String) msg.obj);
+                break;
+            }
             default:
                 if (isCdmaMo()) {
                     mCdmaDispatcher.handleMessage(msg);
@@ -808,7 +817,7 @@
                 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);
+                        tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService);
                 return;
             }
             String scAddr = (String) map.get("scAddr");
@@ -817,7 +826,7 @@
                 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);
+                        tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService);
                 return;
             }
 
@@ -859,7 +868,7 @@
                         + "destPort: %s", scAddr, map.get("destPort")));
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
                 notifySmsSentFailedToEmergencyStateTracker(
-                        tracker.mDestAddress, tracker.mMessageId);
+                        tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService);
                 return;
             }
             // replace old smsc and pdu with newly encoded ones
@@ -1147,13 +1156,16 @@
      * @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.
+     * @param isOverIms A flag specifying whether MO SMS is sent over IMS or not.
      */
     private void handleSmsSentCompletedUsingDomainSelection(@NonNull String destAddr,
-            long messageId, boolean success) {
+            long messageId, boolean success, boolean isOverIms) {
         if (mEmergencyStateTracker != null) {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
             if (tm.isEmergencyNumber(destAddr)) {
-                mEmergencyStateTracker.endSms(String.valueOf(messageId), success);
+                mEmergencyStateTracker.endSms(String.valueOf(messageId), success,
+                        isOverIms ? NetworkRegistrationInfo.DOMAIN_PS
+                                  : NetworkRegistrationInfo.DOMAIN_CS);
             }
         }
     }
@@ -1161,13 +1173,15 @@
     /**
      * Called when MO SMS is successfully sent.
      */
-    protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId) {
+    protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId,
+            boolean isOverIms) {
         if (isSmsDomainSelectionEnabled()) {
             // Run on main thread for interworking with EmergencyStateTracker.
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = destAddr;
             args.arg2 = Long.valueOf(messageId);
             args.arg3 = Boolean.TRUE;
+            args.arg4 = Boolean.valueOf(isOverIms);
             sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args));
         }
     }
@@ -1176,17 +1190,42 @@
      * Called when sending MO SMS is failed.
      */
     protected void notifySmsSentFailedToEmergencyStateTracker(@NonNull String destAddr,
-            long messageId) {
+            long messageId, boolean isOverIms) {
         if (isSmsDomainSelectionEnabled()) {
             // Run on main thread for interworking with EmergencyStateTracker.
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = destAddr;
             args.arg2 = Long.valueOf(messageId);
             args.arg3 = Boolean.FALSE;
+            args.arg4 = Boolean.valueOf(isOverIms);
             sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args));
         }
     }
 
+    /**
+     * Called when MT SMS is received via IMS.
+     *
+     * @param origAddr The originating address of MT SMS.
+     */
+    private void handleSmsReceivedViaIms(@Nullable String origAddr) {
+        if (mEmergencyStateTracker != null) {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            if (origAddr != null && tm.isEmergencyNumber(origAddr)) {
+                mEmergencyStateTracker.onEmergencySmsReceived();
+            }
+        }
+    }
+
+    /**
+     * Called when MT SMS is received via IMS.
+     */
+    protected void notifySmsReceivedViaImsToEmergencyStateTracker(@Nullable String origAddr) {
+        if (isSmsDomainSelectionEnabled()) {
+            // Run on main thread for interworking with EmergencyStateTracker.
+            sendMessage(obtainMessage(EVENT_SMS_RECEIVED_VIA_IMS, origAddr));
+        }
+    }
+
     private boolean isTestEmergencyNumber(String number) {
         try {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 74094a3..5da4b12 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -48,6 +48,8 @@
 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;
@@ -277,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) {
@@ -297,8 +305,9 @@
     /**
      * Create a new EmergencyNumberTracker.
      */
-    public EmergencyNumberTracker makeEmergencyNumberTracker(Phone phone, CommandsInterface ci) {
-        return new EmergencyNumberTracker(phone, ci);
+    public EmergencyNumberTracker makeEmergencyNumberTracker(Phone phone, CommandsInterface ci,
+            @NonNull FeatureFlags featureFlags) {
+        return new EmergencyNumberTracker(phone, ci, featureFlags);
     }
 
     private static final boolean USE_NEW_NITZ_STATE_MACHINE = true;
@@ -342,8 +351,9 @@
      * 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) {
@@ -468,8 +478,8 @@
     }
 
     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,
@@ -498,8 +508,9 @@
      * @param c The context.
      * @return The multi sim settings controller instance.
      */
-    public MultiSimSettingController initMultiSimSettingController(Context c) {
-        return MultiSimSettingController.init(c);
+    public MultiSimSettingController initMultiSimSettingController(Context c,
+            @NonNull FeatureFlags featureFlags) {
+        return MultiSimSettingController.init(c, featureFlags);
     }
 
     /**
@@ -562,13 +573,28 @@
      * @return The data settings manager instance.
      */
     public @NonNull DataSettingsManager makeDataSettingsManager(@NonNull Phone phone,
-            @NonNull DataNetworkController dataNetworkController, @NonNull Looper looper,
+            @NonNull DataNetworkController dataNetworkController,
+            @NonNull FeatureFlags featureFlags, @NonNull Looper looper,
             @NonNull DataSettingsManager.DataSettingsManagerCallback callback) {
-        return new DataSettingsManager(phone, dataNetworkController, looper, callback);
+        return new DataSettingsManager(phone, dataNetworkController, featureFlags, looper,
+                callback);
+    }
+
+    /** Create CellularNetworkSecuritySafetySource. */
+    public CellularNetworkSecuritySafetySource makeCellularNetworkSecuritySafetySource(
+            Context context) {
+        return CellularNetworkSecuritySafetySource.getInstance(context);
     }
 
     /** Create CellularIdentifierDisclosureNotifier. */
-    public CellularIdentifierDisclosureNotifier makeIdentifierDisclosureNotifier() {
-        return CellularIdentifierDisclosureNotifier.getInstance();
+    public CellularIdentifierDisclosureNotifier makeIdentifierDisclosureNotifier(
+            CellularNetworkSecuritySafetySource safetySource) {
+        return CellularIdentifierDisclosureNotifier.getInstance(safetySource);
+    }
+
+    /** Create NullCipherNotifier. */
+    public NullCipherNotifier makeNullCipherNotifier(
+            CellularNetworkSecuritySafetySource safetySource) {
+        return NullCipherNotifier.getInstance(safetySource);
     }
 }
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/TelephonyAnalytics.java b/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java
index d7a70be..e74e40e 100644
--- a/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java
+++ b/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java
@@ -264,7 +264,11 @@
                 callType = CallType.NORMAL_CALL.value;
             }
             if (isOverIms) {
-                disconnectCauseString = sImsCodeMap.get(disconnectCause);
+                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
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/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index 2119003..f40b6d6 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -129,7 +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);
+            notifySmsSentFailedToEmergencyStateTracker(tracker, false);
             return;
         }
 
diff --git a/src/java/com/android/internal/telephony/configupdate/ConfigParser.java b/src/java/com/android/internal/telephony/configupdate/ConfigParser.java
new file mode 100644
index 0000000..5c3ac86
--- /dev/null
+++ b/src/java/com/android/internal/telephony/configupdate/ConfigParser.java
@@ -0,0 +1,100 @@
+/*
+ * 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.configupdate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class ConfigParser<T> {
+
+    public static final int VERSION_UNKNOWN = -1;
+
+    protected int mVersion = VERSION_UNKNOWN;
+
+    protected T mConfig;
+
+    /**
+     * Constructs a parser from the raw data
+     *
+     * @param data the config data
+     */
+    public ConfigParser(@Nullable byte[] data) {
+        parseData(data);
+    }
+
+    /**
+     * Constructs a parser from the input stream
+     *
+     * @param input the input stream of the config
+     * @return the instance of the ConfigParser
+     */
+    public ConfigParser(@NonNull InputStream input) throws IOException {
+        parseData(input.readAllBytes());
+    }
+
+    /**
+     * Constructs a parser from the input stream
+     *
+     * @param file the input file of the config
+     * @return the instance of the ConfigParser
+     */
+    public ConfigParser(@NonNull File file) throws IOException {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            parseData(fis.readAllBytes());
+        }
+    }
+
+    /**
+     * Get the version of the config
+     *
+     * @return the version of the config if it is defined, or VERSION_UNKNOWN
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Get the config
+     *
+     * @return the config
+     */
+    public @Nullable T getConfig() {
+        return mConfig;
+    }
+
+    /**
+     * Get the sub config raw data by id
+     *
+     * @param id the identifier of the sub config
+     * @return the raw data of the sub config
+     */
+    public @Nullable byte[] getData(String id) {
+        return null;
+    }
+
+    /**
+     * Parse the config data
+     *
+     * @param data the config data
+     */
+    protected abstract void parseData(@Nullable byte[] data);
+}
diff --git a/src/java/com/android/internal/telephony/configupdate/ConfigProviderAdaptor.java b/src/java/com/android/internal/telephony/configupdate/ConfigProviderAdaptor.java
new file mode 100644
index 0000000..1344534
--- /dev/null
+++ b/src/java/com/android/internal/telephony/configupdate/ConfigProviderAdaptor.java
@@ -0,0 +1,54 @@
+/*
+ * 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.configupdate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+public interface ConfigProviderAdaptor {
+
+    String DOMAIN_SATELLITE = "satellite";
+
+    /**
+     * Get the config from the provider
+     */
+    @Nullable ConfigParser getConfigParser(String domain);
+
+    class Callback {
+        /**
+         * The callback when the config is changed
+         * @param config the config is changed
+         */
+        public void onChanged(@Nullable ConfigParser config) {}
+    }
+
+    /**
+     * Register the callback to monitor the config change
+     * @param executor The executor to execute the callback.
+     * @param callback the callback to monitor the config change
+     */
+    void registerCallback(@NonNull Executor executor, @NonNull Callback callback);
+
+    /**
+     * Unregister the callback
+     * @param callback the callback to be unregistered
+     */
+    void unregisterCallback(@NonNull Callback callback);
+}
diff --git a/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
new file mode 100644
index 0000000..0db7844
--- /dev/null
+++ b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
@@ -0,0 +1,200 @@
+/*
+ * 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.configupdate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.satellite.SatelliteConfigParser;
+import com.android.server.updates.ConfigUpdateInstallReceiver;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+public class TelephonyConfigUpdateInstallReceiver extends ConfigUpdateInstallReceiver implements
+        ConfigProviderAdaptor {
+
+    private static final String TAG = "TelephonyConfigUpdateInstallReceiver";
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected static final String UPDATE_DIR = "/data/misc/telephonyconfig";
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected static final String UPDATE_CONTENT_PATH = "telephony_config.pb";
+    protected static final String UPDATE_METADATA_PATH = "metadata/";
+    public static final String VERSION = "version";
+
+    private ConcurrentHashMap<Executor, Callback> mCallbackHashMap = new ConcurrentHashMap<>();
+    @NonNull
+    private final Object mConfigParserLock = new Object();
+    @GuardedBy("mConfigParserLock")
+    private ConfigParser mConfigParser;
+
+
+    public static TelephonyConfigUpdateInstallReceiver sReceiverAdaptorInstance =
+            new TelephonyConfigUpdateInstallReceiver();
+
+    /**
+     * @return The singleton instance of TelephonyConfigUpdateInstallReceiver
+     */
+    @NonNull
+    public static TelephonyConfigUpdateInstallReceiver getInstance() {
+        return sReceiverAdaptorInstance;
+    }
+
+    public TelephonyConfigUpdateInstallReceiver() {
+        super(UPDATE_DIR, UPDATE_CONTENT_PATH, UPDATE_METADATA_PATH, VERSION);
+    }
+
+    /**
+     * @return byte array type of config data protobuffer file
+     */
+    @Nullable
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public byte[] getCurrentContent() {
+        try {
+            return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath());
+        } catch (IOException e) {
+            Slog.i(TAG, "Failed to read current content, assuming first update!");
+            return null;
+        }
+    }
+
+    @Override
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public void postInstall(Context context, Intent intent) {
+        Log.d(TAG, "Telephony config is updated in file partition");
+        ConfigParser updatedConfigParser = getNewConfigParser(DOMAIN_SATELLITE,
+                getCurrentContent());
+
+        if (updatedConfigParser == null) {
+            Log.d(TAG, "updatedConfigParser is null");
+            return;
+        }
+
+        boolean isParserChanged = false;
+
+        synchronized (getInstance().mConfigParserLock) {
+            if (getInstance().mConfigParser == null) {
+                getInstance().mConfigParser = updatedConfigParser;
+                isParserChanged = true;
+            } else {
+                int updatedVersion = updatedConfigParser.mVersion;
+                int previousVersion = getInstance().mConfigParser.mVersion;
+                Log.d(TAG, "previous version is " + previousVersion + " | updated version is "
+                        + updatedVersion);
+                if (updatedVersion > previousVersion) {
+                    getInstance().mConfigParser = updatedConfigParser;
+                    isParserChanged = true;
+                }
+            }
+        }
+
+        if (isParserChanged) {
+            if (getInstance().mCallbackHashMap.keySet().isEmpty()) {
+                Log.d(TAG, "mCallbackHashMap.keySet().isEmpty");
+                return;
+            }
+            Iterator<Executor> iterator =
+                    getInstance().mCallbackHashMap.keySet().iterator();
+            while (iterator.hasNext()) {
+                Executor executor = iterator.next();
+                getInstance().mCallbackHashMap.get(executor).onChanged(
+                        updatedConfigParser);
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    public ConfigParser getConfigParser(String domain) {
+        Log.d(TAG, "getConfigParser");
+        synchronized (getInstance().mConfigParserLock) {
+            if (getInstance().mConfigParser == null) {
+                Log.d(TAG, "CreateNewConfigParser with domain " + domain);
+                getInstance().mConfigParser = getNewConfigParser(domain, getCurrentContent());
+            }
+            return getInstance().mConfigParser;
+        }
+    }
+
+    @Override
+    public void registerCallback(@NonNull Executor executor, @NonNull Callback callback) {
+        mCallbackHashMap.put(executor, callback);
+    }
+
+    @Override
+    public void unregisterCallback(@NonNull Callback callback) {
+        Iterator<Executor> iterator = mCallbackHashMap.keySet().iterator();
+        while (iterator.hasNext()) {
+            Executor executor = iterator.next();
+            if (mCallbackHashMap.get(executor) == callback) {
+                mCallbackHashMap.remove(executor);
+                break;
+            }
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public File getUpdateDir() {
+        return getInstance().updateDir;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public File getUpdateContent() {
+        return getInstance().updateContent;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public ConcurrentHashMap<Executor, Callback> getCallbackMap() {
+        return getInstance().mCallbackHashMap;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void setCallbackMap(ConcurrentHashMap<Executor, Callback> map) {
+        getInstance().mCallbackHashMap = map;
+    }
+
+    /**
+     * @param data byte array type of config data
+     * @return when data is null, return null otherwise return ConfigParser
+     */
+    @Nullable
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public ConfigParser getNewConfigParser(String domain, @Nullable byte[] data) {
+        if (data == null) {
+            Log.d(TAG, "content data is null");
+            return null;
+        }
+        switch (domain) {
+            case DOMAIN_SATELLITE:
+                return new SatelliteConfigParser(data);
+            default:
+                Log.e(TAG, "DOMAIN should be specified");
+                return null;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
index a657ecd..3d3fbe9 100644
--- a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
@@ -234,6 +234,11 @@
                     .mapToObj(AccessNetworkType::toString).collect(Collectors.joining(","))
                     + "]");
 
+            handleQualifiedNetworksChanged(apnTypes, qualifiedNetworkTypes, false);
+        }
+
+        private void handleQualifiedNetworksChanged(
+                int apnTypes, int[] qualifiedNetworkTypes, boolean forceReconnect) {
             if (Arrays.stream(qualifiedNetworkTypes).anyMatch(accessNetwork
                     -> !DataUtils.isValidAccessNetwork(accessNetwork))) {
                 loge("Invalid access networks " + Arrays.toString(qualifiedNetworkTypes));
@@ -274,8 +279,9 @@
                                     AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
                             mAccessNetworksManagerCallbacks.forEach(callback ->
                                     callback.invokeFromExecutor(() ->
-                                            callback.onPreferredTransportChanged(DataUtils
-                                                    .apnTypeToNetworkCapability(apnType))));
+                                            callback.onPreferredTransportChanged(
+                                                    DataUtils.apnTypeToNetworkCapability(apnType),
+                                                    forceReconnect)));
                         }
                     } else {
                         mAvailableNetworks.put(apnType, qualifiedNetworkTypes);
@@ -297,7 +303,7 @@
             }
 
             if (!qualifiedNetworksList.isEmpty()) {
-                setPreferredTransports(qualifiedNetworksList);
+                setPreferredTransports(qualifiedNetworksList, forceReconnect);
                 mQualifiedNetworksChangedRegistrants.notifyResult(qualifiedNetworksList);
             }
         }
@@ -338,6 +344,17 @@
                 }
             });
         }
+
+        @Override
+        public void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType) {
+            if (mFeatureFlags.reconnectQualifiedNetwork()) {
+                log("onReconnectQualifedNetworkType: apnTypes = ["
+                        + ApnSetting.getApnTypesStringFromBitmask(apnTypes)
+                        + "], networks = [" + AccessNetworkType.toString(qualifiedNetworkType)
+                        + "]");
+                handleQualifiedNetworksChanged(apnTypes, new int[]{qualifiedNetworkType}, true);
+            }
+        }
     }
 
     private void onEmergencyDataNetworkPreferredTransportChanged(
@@ -371,8 +388,10 @@
          * Called when preferred transport changed.
          *
          * @param networkCapability The network capability.
+         * @param forceReconnect whether enforce reconnection to the preferred transport type.
          */
-        public abstract void onPreferredTransportChanged(@NetCapability int networkCapability);
+        public abstract void onPreferredTransportChanged(
+                @NetCapability int networkCapability, boolean forceReconnect);
     }
 
     /**
@@ -610,7 +629,8 @@
                 : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
     }
 
-    private void setPreferredTransports(@NonNull List<QualifiedNetworks> networksList) {
+    private void setPreferredTransports(
+            @NonNull List<QualifiedNetworks> networksList, boolean forceReconnect) {
         for (QualifiedNetworks networks : networksList) {
             if (networks.qualifiedNetworks.length > 0) {
                 int transport = getTransportFromAccessNetwork(networks.qualifiedNetworks[0]);
@@ -618,11 +638,13 @@
                     mPreferredTransports.put(networks.apnType, transport);
                     mAccessNetworksManagerCallbacks.forEach(callback ->
                             callback.invokeFromExecutor(() ->
-                                    callback.onPreferredTransportChanged(DataUtils
-                                            .apnTypeToNetworkCapability(networks.apnType))));
+                                    callback.onPreferredTransportChanged(
+                                            DataUtils.apnTypeToNetworkCapability(networks.apnType),
+                                            forceReconnect)));
                     logl("setPreferredTransports: apnType="
                             + ApnSetting.getApnTypeString(networks.apnType) + ", transport="
-                            + AccessNetworkConstants.transportTypeToString(transport));
+                            + AccessNetworkConstants.transportTypeToString(transport)
+                            + (forceReconnect ? ", forceReconnect:true" : ""));
                 }
             }
         }
@@ -643,7 +665,7 @@
      * Get the  preferred transport by network capability.
      *
      * @param networkCapability The network capability. (Note that only APN-type capabilities are
-     * supported.
+     * supported.)
      * @return The preferred transport.
      */
     public @TransportType int getPreferredTransportByNetworkCapability(
@@ -662,7 +684,7 @@
      * @return {@code true} if there is any APN is on IWLAN, otherwise {@code false}.
      */
     public boolean isAnyApnOnIwlan() {
-        for (int apnType : AccessNetworksManager.SUPPORTED_APN_TYPES) {
+        for (int apnType : SUPPORTED_APN_TYPES) {
             if (getPreferredTransport(apnType) == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
                 return true;
             }
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
index e8cd8f0..343bb0b 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,13 +35,14 @@
 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;
@@ -48,6 +50,7 @@
 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;
@@ -59,7 +62,10 @@
 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;
 
 /**
@@ -105,7 +111,7 @@
     /** 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;
 
@@ -124,22 +130,54 @@
     /** 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 final @NonNull FeatureFlags mFlags;
+    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.
@@ -150,6 +188,12 @@
      * even if ping test fails.
      */
     private boolean mRequirePingTestBeforeSwitch = true;
+    /**
+     * TODO: remove after V.
+     * To indicate whether allow using roaming nDDS if user enabled its roaming when the DDS is not
+     * usable(OOS or disabled roaming)
+     */
+    private boolean mAllowNddsRoamning = true;
     /** The count of consecutive auto switch validation failure **/
     private int mAutoSwitchValidationFailedCount = 0;
     /**
@@ -173,7 +217,10 @@
          * How preferred the current phone is.
          */
         enum UsableState {
-            HOME(1), ROAMING_ENABLED(0), NOT_USABLE(-1);
+            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.
@@ -186,8 +233,7 @@
         /** The phone */
         @NonNull private final Phone mPhone;
         /** Data registration state of the phone */
-        @RegistrationState private int mDataRegState = NetworkRegistrationInfo
-                .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+        @RegistrationState private int mDataRegState;
         /** Current Telephony display info of the phone */
         @NonNull private TelephonyDisplayInfo mDisplayInfo;
         /** Signal strength of the phone */
@@ -196,6 +242,10 @@
         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();
         }
@@ -214,12 +264,24 @@
          * @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:
-                    return mPhone.getDataRoamingEnabled()
-                            ? UsableState.ROAMING_ENABLED : UsableState.NOT_USABLE;
+                    if (mPhone.getDataRoamingEnabled()) {
+                        if (isUsingNonTerrestrialNetwork) {
+                            return UsableState.NON_TERRESTRIAL;
+                        }
+                        return UsableState.ROAMING_ENABLED;
+                    }
+                    return UsableState.NOT_USABLE;
                 default:
                     return UsableState.NOT_USABLE;
             }
@@ -230,8 +292,8 @@
             return "{phone " + mPhone.getPhoneId()
                     + " score=" + getRatSignalScore() + " dataRegState="
                     + NetworkRegistrationInfo.registrationStateToString(mDataRegState)
-                    + " " + getUsableState()
-                    + " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel()
+                    + " " + getUsableState() + " " + mDisplayInfo
+                    + " signalStrength=" + mSignalStrength.getLevel()
                     + " listeningForEvents=" + mListeningForEvents
                     + "}";
 
@@ -275,10 +337,13 @@
             @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) {
         super(looper);
         mContext = context;
-        mFlags = featureFlags;
+        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];
@@ -385,8 +450,11 @@
         DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
         mScoreTolerance =  dataConfig.getAutoDataSwitchScoreTolerance();
         mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
+        mAllowNddsRoamning = dataConfig.doesAutoDataSwitchAllowRoaming();
         mAutoDataSwitchAvailabilityStabilityTimeThreshold =
                 dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        mAutoDataSwitchPerformanceStabilityTimeThreshold =
+                dataConfig.getAutoDataSwitchPerformanceStabilityTimeThreshold();
         mAutoDataSwitchValidationMaxRetry =
                 dataConfig.getAutoDataSwitchValidationMaxRetry();
     }
@@ -412,15 +480,35 @@
                 onSignalStrengthChanged(phoneId);
                 break;
             case EVENT_EVALUATE_AUTO_SWITCH:
-                int reason = (int) msg.obj;
-                onEvaluateAutoDataSwitch(reason);
+                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_MEETS_AUTO_DATA_SWITCH_STATE:
-                int targetPhoneId = msg.arg1;
-                boolean needValidation = msg.arg2 == 1;
-                log("require validation on phone " + targetPhoneId
-                        + (needValidation ? "" : " no") + " need to pass");
-                mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
+            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();
@@ -513,16 +601,21 @@
 
     /**
      * 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.
+     * @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();
-        if (isActiveModemPhone(preferredPhoneId)) {
+        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 (phoneId != preferredPhoneId
-                        && (candidateScore - currentScore) > mScoreTolerance) {
+                if ((candidateScore - currentScore) > mScoreTolerance
+                        // Also reevaluate if DDS has the same score as the current phone.
+                        || (candidateScore >= currentScore && phoneId == ddsPhoneId)) {
                     return phoneId;
                 }
             }
@@ -539,8 +632,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);
+            }
         }
     }
 
@@ -556,14 +656,15 @@
         int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
         // check is valid DSDS
         if (mSubscriptionManagerService.getActiveSubIdList(true).length < 2) return;
-        Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId(
-                defaultDataSubId));
+        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();
         StringBuilder debugMessage = new StringBuilder("onEvaluateAutoDataSwitch:");
         debugMessage.append(" defaultPhoneId: ").append(defaultDataPhoneId)
@@ -571,11 +672,11 @@
                 .append(", reason: ").append(evaluationReasonToString(reason));
         if (preferredPhoneId == defaultDataPhoneId) {
             // on default data sub
-            int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId, debugMessage);
+            StabilityEventExtra res = evaluateAnyCandidateToUse(defaultDataPhoneId, debugMessage);
             log(debugMessage.toString());
-            if (candidatePhoneId != INVALID_PHONE_INDEX) {
-                mSelectedTargetPhoneId = candidatePhoneId;
-                startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch);
+            if (res.targetPhoneId != INVALID_PHONE_INDEX) {
+                mSelectedTargetPhoneId = res.targetPhoneId;
+                startStabilityCheck(res.targetPhoneId, res.isForPerformance, res.needValidation);
             } else {
                 cancelAnyPendingSwitch();
             }
@@ -597,9 +698,10 @@
             }
 
             boolean backToDefault = false;
+            boolean isForPerformance = false;
             boolean needValidation = true;
 
-            if (mFlags.autoSwitchAllowRoaming()) {
+            if (isNddsRoamingEnabled()) {
                 if (mDefaultNetworkIsOnNonCellular) {
                     debugMessage.append(", back to default as default network")
                             .append(" is active on nonCellular transport");
@@ -634,12 +736,13 @@
                                         .getRatSignalScore();
                                 int currentScore = mPhonesSignalStatus[preferredPhoneId]
                                         .getRatSignalScore();
-                                if ((defaultScore - currentScore) > mScoreTolerance) {
+                                if (defaultScore >= currentScore) {
                                     debugMessage
-                                            .append(", back to default for higher score ")
+                                            .append(", back to default for higher or equal score ")
                                             .append(defaultScore).append(" versus current ")
                                             .append(currentScore);
                                     backToDefault = true;
+                                    isForPerformance = true;
                                     needValidation = mRequirePingTestBeforeSwitch;
                                 }
                             } else {
@@ -668,12 +771,13 @@
                 } else if (isRatSignalStrengthBasedSwitchEnabled()) {
                     int defaultScore = mPhonesSignalStatus[defaultDataPhoneId].getRatSignalScore();
                     int currentScore = mPhonesSignalStatus[preferredPhoneId].getRatSignalScore();
-                    if ((defaultScore - currentScore) > mScoreTolerance) {
+                    if (defaultScore >= currentScore) {
                         debugMessage
-                                .append(", back to default as default phone has higher score ")
+                                .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)) {
@@ -686,7 +790,7 @@
             if (backToDefault) {
                 log(debugMessage.toString());
                 mSelectedTargetPhoneId = defaultDataPhoneId;
-                startStabilityCheck(DEFAULT_PHONE_INDEX, needValidation);
+                startStabilityCheck(DEFAULT_PHONE_INDEX, isForPerformance, needValidation);
             } else {
                 // cancel any previous attempts of switching back to default phone
                 cancelAnyPendingSwitch();
@@ -698,40 +802,44 @@
      * Called when consider switching from primary default data sub to another data sub.
      * @param defaultPhoneId The default data phone
      * @param debugMessage Debug message.
-     * @return the target subId if a suitable candidate is found, otherwise return
-     * {@link SubscriptionManager#INVALID_PHONE_INDEX}
+     * @return StabilityEventExtra As evaluation result.
      */
-    private int getSwitchCandidatePhoneId(int defaultPhoneId, @NonNull StringBuilder debugMessage) {
+    @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) {
             debugMessage.append(", no candidate as no sim loaded");
-            return INVALID_PHONE_INDEX;
+            return invalidResult;
         }
 
         if (!defaultDataPhone.isUserDataEnabled()) {
             debugMessage.append(", no candidate as user disabled mobile data");
-            return INVALID_PHONE_INDEX;
+            return invalidResult;
         }
 
         if (mDefaultNetworkIsOnNonCellular) {
             debugMessage.append(", no candidate as default network is active")
                     .append(" on non-cellular transport");
-            return INVALID_PHONE_INDEX;
+            return invalidResult;
         }
 
-        if (mFlags.autoSwitchAllowRoaming()) {
+        if (isNddsRoamingEnabled()) {
             // 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 INVALID_PHONE_INDEX;
+                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 INVALID_PHONE_INDEX;
+                return invalidResult;
             }
         }
 
@@ -741,30 +849,30 @@
 
             Phone secondaryDataPhone = null;
             PhoneSignalStatus candidatePhoneStatus = mPhonesSignalStatus[phoneId];
-            if (mFlags.autoSwitchAllowRoaming()) {
+            if (isNddsRoamingEnabled()) {
                 PhoneSignalStatus.UsableState currentUsableState =
                         mPhonesSignalStatus[defaultPhoneId].getUsableState();
-                PhoneSignalStatus.UsableState candidatePhoneUsableRank =
+                PhoneSignalStatus.UsableState candidateUsableState =
                         mPhonesSignalStatus[phoneId].getUsableState();
-                debugMessage.append(", found phone ").append(phoneId).append(" is ").append(
-                        candidatePhoneUsableRank)
-                        .append(", current is ").append(currentUsableState);
-                if (candidatePhoneUsableRank.mScore > currentUsableState.mScore) {
+                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 == candidatePhoneUsableRank.mScore) {
+                        && 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 higher score ").append(
-                                        candidateScore)
-                                .append(" versus current ").append(defaultScore);
+                        debugMessage.append(" with ").append(defaultScore)
+                                .append(" versus candidate higher score ").append(candidateScore);
                         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 ")
+                        debugMessage.append(", candidate's score ").append(candidateScore)
+                                .append(" doesn't justify the switch given the current ")
                                 .append(defaultScore);
                     }
                 }
@@ -782,6 +890,7 @@
                             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 ")
@@ -797,21 +906,30 @@
             if (secondaryDataPhone != null) {
                 // check auto switch feature enabled
                 if (secondaryDataPhone.isDataAllowed()) {
-                    return phoneId;
+                    return new StabilityEventExtra(phoneId,
+                            isForPerformance, mRequirePingTestBeforeSwitch);
                 } else {
-                    debugMessage.append(", but its data is not allowed");
+                    debugMessage.append(", but candidate's data is not allowed");
                 }
             }
         }
         debugMessage.append(", found no qualified candidate.");
-        return INVALID_PHONE_INDEX;
+        return invalidResult;
     }
 
     /**
      * @return {@code true} If the feature of switching base on RAT and signal strength is enabled.
      */
     private boolean isRatSignalStrengthBasedSwitchEnabled() {
-        return mFlags.autoDataSwitchRatSs() && mScoreTolerance >= 0;
+        return sFeatureFlags.autoDataSwitchRatSs() && mScoreTolerance >= 0
+                && mAutoDataSwitchPerformanceStabilityTimeThreshold >= 0;
+    }
+
+    /**
+     * @return {@code true} If the feature of switching to roaming non DDS is enabled.
+     */
+    private boolean isNddsRoamingEnabled() {
+        return sFeatureFlags.autoDataSwitchAllowRoaming() && mAllowNddsRoamning;
     }
 
     /**
@@ -819,18 +937,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);
+    private void startStabilityCheck(int targetPhoneId, boolean isForPerformance,
+            boolean needValidation) {
         String combinationIdentifier = targetPhoneId + "" + needValidation;
-        if (!hasEqualMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, combinationIdentifier)) {
-            removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
-            sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId,
+        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);
         }
     }
 
@@ -886,7 +1063,19 @@
     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();
     }
 
@@ -900,8 +1089,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;
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index ea7b1da..0e06dad 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -870,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() {
@@ -1043,6 +1051,15 @@
     }
 
     /**
+     * TODO: remove after V.
+     * @return To indicate whether allow using roaming nDDS if user enabled its roaming when the DDS
+     * is not usable(OOS or disabled roaming)
+     */
+    public boolean doesAutoDataSwitchAllowRoaming() {
+        return mResources.getBoolean(com.android.internal.R.bool.auto_data_switch_allow_roaming);
+    }
+
+    /**
      * @return The maximum number of retries when a validation for switching failed.
      */
     public int getAutoDataSwitchValidationMaxRetry() {
@@ -1061,6 +1078,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"
@@ -1398,6 +1425,17 @@
     }
 
     /**
+     * @return Indicating whether the retry timer from setup data call response for data throttling
+     * should be honored for emergency network request. By default this is off, meaning emergency
+     * network requests will ignore the previous retry timer passed in from setup data call
+     * response.
+     */
+    public boolean shouldHonorRetryTimerForEmergencyNetworkRequest() {
+        return mResources.getBoolean(
+                com.android.internal.R.bool.config_honor_data_retry_timer_for_emergency_network);
+    }
+
+    /**
      * Log debug messages.
      * @param s debug messages
      */
@@ -1440,6 +1478,8 @@
         pw.increaseIndent();
         mDataHandoverRetryRules.forEach(pw::println);
         pw.decreaseIndent();
+        pw.println("shouldHonorRetryTimerForEmergencyNetworkRequest="
+                + shouldHonorRetryTimerForEmergencyNetworkRequest());
         pw.println("mSetupDataCallAnomalyReport=" + mSetupDataCallAnomalyReportThreshold);
         pw.println("mNetworkUnwantedAnomalyReport=" + mNetworkUnwantedAnomalyReportThreshold);
         pw.println("mImsReleaseRequestAnomalyReport=" + mImsReleaseRequestAnomalyReportThreshold);
@@ -1456,6 +1496,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()
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index 6858c6a..3d10e9c 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -257,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,
@@ -342,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 bba0f19..0dbbc5c 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;
@@ -91,6 +92,7 @@
 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;
@@ -120,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;
@@ -310,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 {}
 
@@ -406,6 +410,9 @@
     /** 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 //
     //********************************************************************************************//
@@ -743,6 +750,11 @@
     private @Nullable Consumer<Integer> mNetworkValidationResultCodeCallback;
 
     /**
+     * Callback used to listen QNS preference changes.
+     */
+    private @Nullable AccessNetworksManagerCallback mAccessNetworksManagerCallback;
+
+    /**
      * The network bandwidth.
      */
     public static class NetworkBandwidth {
@@ -910,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);
     }
 
     /**
@@ -1160,18 +1180,37 @@
 
             mPhone.getServiceStateTracker().registerForCssIndicatorChanged(
                     getHandler(), EVENT_CSS_INDICATOR_CHANGED, null);
-            mPhone.getCallTracker().registerForVoiceCallStarted(
-                    getHandler(), EVENT_VOICE_CALL_STARTED, null);
-            mPhone.getCallTracker().registerForVoiceCallEnded(
-                    getHandler(), EVENT_VOICE_CALL_ENDED, null);
+            if (mPhone.getCallTracker() != null) {
+                mPhone.getCallTracker().registerForVoiceCallStarted(
+                        getHandler(), EVENT_VOICE_CALL_STARTED, null);
+                mPhone.getCallTracker().registerForVoiceCallEnded(
+                        getHandler(), EVENT_VOICE_CALL_ENDED, null);
+            }
             // Check null for devices not supporting FEATURE_TELEPHONY_IMS.
-            if (mPhone.getImsPhone() != null) {
+            if (mPhone.getImsPhone() != null && mPhone.getImsPhone().getCallTracker() != null) {
                 mPhone.getImsPhone().getCallTracker().registerForVoiceCallStarted(
                         getHandler(), EVENT_VOICE_CALL_STARTED, null);
                 mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded(
                         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, boolean forceReconnect) {
+                            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
@@ -1181,13 +1220,19 @@
         @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) {
+            if (mPhone.getImsPhone() != null && mPhone.getImsPhone().getCallTracker() != null) {
                 mPhone.getImsPhone().getCallTracker().unregisterForVoiceCallStarted(getHandler());
                 mPhone.getImsPhone().getCallTracker().unregisterForVoiceCallEnded(getHandler());
             }
-            mPhone.getCallTracker().unregisterForVoiceCallStarted(getHandler());
-            mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler());
+            if (mPhone.getCallTracker() != null) {
+                mPhone.getCallTracker().unregisterForVoiceCallStarted(getHandler());
+                mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler());
+            }
 
             mPhone.getServiceStateTracker().unregisterForCssIndicatorChanged(getHandler());
             TelephonyManager tm = mPhone.getContext().getSystemService(TelephonyManager.class);
@@ -1366,7 +1411,14 @@
                     } else {
                         loge("Failed to allocate PDU session id. e=" + ar.exception);
                     }
-                    setupData();
+                    //Check whether all network requests were removed before setupData.
+                    if (!mAttachedNetworkRequestList.isEmpty()) {
+                        setupData();
+                    } else {
+                        mRetryDelayMillis = DataCallResponse.RETRY_DURATION_UNDEFINED;
+                        mFailCause = DataFailCause.NO_RETRY_FAILURE;
+                        transitionTo(mDisconnectedState);
+                    }
                     break;
                 case EVENT_SETUP_DATA_NETWORK_RESPONSE:
                     int resultCode = msg.arg1;
@@ -1495,7 +1547,7 @@
                 //  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) {
+                if (mAttachedNetworkRequestList.isEmpty()) {
                     log("Tear down the network since there is no live network request.");
                     // Directly call onTearDown here. Calling tearDown will cause deadlock because
                     // EVENT_TEAR_DOWN_NETWORK is deferred until state machine enters connected
@@ -2333,6 +2385,35 @@
             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())
@@ -2437,7 +2518,8 @@
             newSuspendedState = true;
             // Check voice/data concurrency.
         } else if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()
-                && mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+                && mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                && mPhone.getCallTracker() != null) {
             newSuspendedState = mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE;
         }
 
@@ -2615,6 +2697,12 @@
 
         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) {
@@ -3578,7 +3666,7 @@
         mNetworkValidationResultCodeCallback = resultCodeCallback;
 
         // Request validation directly from the data service.
-        mDataServiceManagers.get(mTransport).requestValidation(
+        mDataServiceManagers.get(mTransport).requestNetworkValidation(
                 mCid.get(mTransport), obtainMessage(EVENT_DATA_NETWORK_VALIDATION_RESPONSE));
         log("handleDataNetworkValidationRequest, network validation requested");
     }
@@ -3700,6 +3788,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 + ")";
         }
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 63fe7e5..70d3b23 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -20,15 +20,19 @@
 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;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 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;
@@ -66,6 +70,7 @@
 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;
@@ -238,6 +243,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);
@@ -410,6 +427,12 @@
         }
     };
 
+    private boolean hasCalling() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mPhone.getContext().getPackageManager().hasSystemFeature(
+            PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
+
     /**
      * The sorted network request list by priority. The highest priority network request stays at
      * the head of the list. The highest priority is 100, the lowest is 0.
@@ -632,6 +655,13 @@
          * @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) {}
     }
 
     /**
@@ -838,7 +868,7 @@
 
         mDataSettingsManager = TelephonyComponentFactory.getInstance().inject(
                 DataSettingsManager.class.getName())
-                .makeDataSettingsManager(mPhone, this, looper,
+                .makeDataSettingsManager(mPhone, this, mFeatureFlags, looper,
                         new DataSettingsManagerCallback(this::post) {
                             @Override
                             public void onDataEnabledChanged(boolean enabled,
@@ -898,7 +928,7 @@
                             }
                         });
         mDataStallRecoveryManager = new DataStallRecoveryManager(mPhone, this, mDataServiceManagers
-                .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), looper,
+                .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), mFeatureFlags, looper,
                 new DataStallRecoveryManagerCallback(this::post) {
                     @Override
                     public void onDataStallReestablishInternet() {
@@ -952,13 +982,16 @@
 
         mAccessNetworksManager.registerCallback(new AccessNetworksManagerCallback(this::post) {
             @Override
-            public void onPreferredTransportChanged(@NetCapability int capability) {
+            public void onPreferredTransportChanged(
+                    @NetCapability int capability, boolean forceReconnect) {
                 int preferredTransport = mAccessNetworksManager
                         .getPreferredTransportByNetworkCapability(capability);
                 logl("onPreferredTransportChanged: "
                         + DataUtils.networkCapabilityToString(capability) + " preferred on "
-                        + AccessNetworkConstants.transportTypeToString(preferredTransport));
-                DataNetworkController.this.onEvaluatePreferredTransport(capability);
+                        + AccessNetworkConstants.transportTypeToString(preferredTransport)
+                        + (forceReconnect ? "forceReconnect:true" : ""));
+
+                DataNetworkController.this.onEvaluatePreferredTransport(capability, forceReconnect);
                 if (!hasMessages(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS)) {
                     sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
                             DataEvaluationReason.PREFERRED_TRANSPORT_CHANGED));
@@ -1019,16 +1052,18 @@
                     }
                 }, this::post);
 
-        // Register for call ended event for voice/data concurrent not supported case. It is
-        // intended to only listen for events from the same phone as most of the telephony modules
-        // are designed as per-SIM basis. For DSDS call ended on non-DDS sub, the frameworks relies
-        // on service state on DDS sub change from out-of-service to in-service to trigger data
-        // retry.
-        mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null);
-        // Check null for devices not supporting FEATURE_TELEPHONY_IMS.
-        if (mPhone.getImsPhone() != null) {
-            mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded(
-                    this, EVENT_VOICE_CALL_ENDED, null);
+        if (hasCalling()) {
+            // Register for call ended event for voice/data concurrent not supported case. It is
+            // intended to only listen for events from the same phone as most of the telephony
+            // modules are designed as per-SIM basis. For DSDS call ended on non-DDS sub, the
+            // frameworks relies on service state on DDS sub change from out-of-service to
+            // in-service to trigger data retry.
+            mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null);
+            // Check null for devices not supporting FEATURE_TELEPHONY_IMS.
+            if (mPhone.getImsPhone() != null) {
+                mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded(
+                        this, EVENT_VOICE_CALL_ENDED, null);
+            }
         }
         mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICE_CONFIG_CHANGED, null);
         mPhone.mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
@@ -1142,7 +1177,7 @@
                 }
                 break;
             case EVENT_EVALUATE_PREFERRED_TRANSPORT:
-                onEvaluatePreferredTransport(msg.arg1);
+                onEvaluatePreferredTransport(msg.arg1, msg.arg2 != 0 /* forceReconnect */);
                 break;
             case EVENT_SUBSCRIPTION_PLANS_CHANGED:
                 SubscriptionPlan[] plans = (SubscriptionPlan[]) msg.obj;
@@ -1408,22 +1443,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;
         }
 
@@ -1498,11 +1537,27 @@
 
         // Bypass all checks for emergency network request.
         if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
-            evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
-            evaluation.setCandidateDataProfile(mDataProfileManager.getDataProfileForNetworkRequest(
+            DataProfile emergencyProfile = mDataProfileManager.getDataProfileForNetworkRequest(
                     networkRequest, getDataNetworkType(transport),
                     mServiceState.isUsingNonTerrestrialNetwork(),
-                    isEsimBootStrapProvisioningActivated(), true));
+                    isEsimBootStrapProvisioningActivated(), true);
+
+            // Check if the profile is being throttled.
+            if (mDataConfigManager.shouldHonorRetryTimerForEmergencyNetworkRequest()
+                    && emergencyProfile != null
+                    && mDataRetryManager.isDataProfileThrottled(emergencyProfile, transport)) {
+                evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_THROTTLED);
+                log("Emergency network request is throttled by the previous setup data "
+                            + "call response.");
+                log(evaluation.toString());
+                networkRequest.setEvaluation(evaluation);
+                return evaluation;
+            }
+
+            evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
+            if (emergencyProfile != null) {
+                evaluation.setCandidateDataProfile(emergencyProfile);
+            }
             networkRequest.setEvaluation(evaluation);
             log(evaluation.toString());
             return evaluation;
@@ -1523,7 +1578,7 @@
         }
 
         // Check CS call state and see if concurrent voice/data is allowed.
-        if (mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE
+        if (hasCalling() && mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE
                 && !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
             evaluation.addDataDisallowedReason(
                     DataDisallowedReason.CONCURRENT_VOICE_DATA_NOT_ALLOWED);
@@ -1678,7 +1733,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);
@@ -1695,6 +1757,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}.
      */
@@ -1780,6 +1897,25 @@
             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())
@@ -2162,6 +2298,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;
@@ -2668,6 +2806,14 @@
                         DataNetworkController.this.onRetryUnsatisfiedNetworkRequest(
                                 networkRequest);
                     }
+
+                    @Override
+                    public void onQosSessionsChanged(
+                            @NonNull List<QosBearerSession> qosBearerSessions) {
+                        mDataNetworkControllerCallbacks.forEach(
+                                callback -> callback.invokeFromExecutor(() ->
+                                        callback.onQosSessionsChanged(qosBearerSessions)));
+                    }
                 }
         ));
         if (!mAnyDataNetworkExisting) {
@@ -2790,6 +2936,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);
+        }
     }
 
     /**
@@ -3219,8 +3371,12 @@
      * Called when needed to evaluate the preferred transport for certain capability.
      *
      * @param capability The network capability to evaluate.
+     * @param forceReconnect indicates whether enforce reconnection to move to the preferred
+     *                       transport type.
+     *
      */
-    private void onEvaluatePreferredTransport(@NetCapability int capability) {
+    private void onEvaluatePreferredTransport(
+            @NetCapability int capability, boolean forceReconnect) {
         int preferredTransport = mAccessNetworksManager
                 .getPreferredTransportByNetworkCapability(capability);
         log("onEvaluatePreferredTransport: " + DataUtils.networkCapabilityToString(capability)
@@ -3244,7 +3400,13 @@
                     continue;
                 }
 
-                tryHandoverDataNetwork(dataNetwork, preferredTransport, null/*handoverRetryEntry*/);
+                if (forceReconnect) {
+                    tearDownGracefully(
+                            dataNetwork, DataNetwork.TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED);
+                } else {
+                    tryHandoverDataNetwork(
+                            dataNetwork, preferredTransport, null/*handoverRetryEntry*/);
+                }
             }
         }
     }
@@ -3780,6 +3942,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) {
@@ -4030,6 +4197,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 b4055a3..51fc71b 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -34,6 +34,7 @@
 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;
@@ -286,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);
                     }
                 }
@@ -818,8 +820,13 @@
                 })
                 .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;
         }
 
diff --git a/src/java/com/android/internal/telephony/data/DataServiceManager.java b/src/java/com/android/internal/telephony/data/DataServiceManager.java
index 640399e..ee66a6a 100644
--- a/src/java/com/android/internal/telephony/data/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/data/DataServiceManager.java
@@ -959,8 +959,8 @@
      * @param onCompleteMessage The result message for this request. Null if the client does not
      * care about the result.
      */
-    public void requestValidation(int cid, @Nullable Message onCompleteMessage) {
-        if (DBG) log("requestValidation");
+    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);
@@ -978,9 +978,9 @@
             mMessageMap.put(callback.asBinder(), onCompleteMessage);
         }
         try {
-            mIDataService.requestValidation(mPhone.getPhoneId(), cid, callback);
+            mIDataService.requestNetworkValidation(mPhone.getPhoneId(), cid, callback);
         } catch (RemoteException e) {
-            loge("Cannot invoke requestValidation on data service.");
+            loge("Cannot invoke requestNetworkValidation on data service.");
             if (callback != null) {
                 mMessageMap.remove(callback.asBinder());
             }
diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
index e54f6d3..51e5b7d 100644
--- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java
+++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -47,6 +48,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SettingsObserver;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.DeviceTelephonyPropertiesStats;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
@@ -57,6 +59,7 @@
 import java.io.PrintWriter;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
@@ -88,6 +91,7 @@
     private static final int EVENT_INITIALIZE = 11;
 
     private final Phone mPhone;
+    private final @NonNull FeatureFlags mFeatureFlags;
     private final ContentResolver mResolver;
     private final SettingsObserver mSettingsObserver;
     private final String mLogTag;
@@ -178,10 +182,12 @@
      * @param callback Data settings manager callback.
      */
     public DataSettingsManager(@NonNull Phone phone,
-            @NonNull DataNetworkController dataNetworkController, @NonNull Looper looper,
+            @NonNull DataNetworkController dataNetworkController,
+            @NonNull FeatureFlags featureFlags, @NonNull Looper looper,
             @NonNull DataSettingsManagerCallback callback) {
         super(looper);
         mPhone = phone;
+        mFeatureFlags = Objects.requireNonNull(featureFlags);
         mLogTag = "DSMGR-" + mPhone.getPhoneId();
         log("DataSettingsManager created.");
         mSubId = mPhone.getSubId();
@@ -264,6 +270,12 @@
         }
     }
 
+    private boolean hasCalling() {
+        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        return mPhone.getContext().getPackageManager().hasSystemFeature(
+            PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
+
     /**
      * Called when needed to register for all events that data network controller is interested.
      */
@@ -281,9 +293,12 @@
         mSettingsObserver.observe(
                 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED),
                 EVENT_PROVISIONING_DATA_ENABLED_CHANGED);
-        mPhone.getCallTracker().registerForVoiceCallStarted(this, EVENT_CALL_STATE_CHANGED, null);
-        mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_CALL_STATE_CHANGED, null);
-        if (mPhone.getImsPhone() != null) {
+        if (hasCalling()) {
+            mPhone.getCallTracker().registerForVoiceCallStarted(this, EVENT_CALL_STATE_CHANGED,
+                    null);
+            mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_CALL_STATE_CHANGED, null);
+        }
+        if (hasCalling() && mPhone.getImsPhone() != null) {
             mPhone.getImsPhone().getCallTracker().registerForVoiceCallStarted(
                     this, EVENT_CALL_STATE_CHANGED, null);
             mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded(
diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index f2b6776..ee8890a 100644
--- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
@@ -48,6 +48,7 @@
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.DataStallRecoveryStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.telephony.Rlog;
@@ -153,6 +154,7 @@
     private final @NonNull Phone mPhone;
     private final @NonNull String mLogTag;
     private final @NonNull LocalLog mLocalLog = new LocalLog(128);
+    private final @NonNull FeatureFlags mFeatureFlags;
 
     /** Data network controller */
     private final @NonNull DataNetworkController mDataNetworkController;
@@ -196,7 +198,10 @@
     private boolean mIsInternetNetworkConnected;
     /** The durations for current recovery action */
     private @ElapsedRealtimeLong long mTimeElapsedOfCurrentAction;
-
+    /** Tracks the total number of validation duration a data stall */
+    private int mValidationCount;
+    /** Tracks the number of validation for current action during a data stall */
+    private int mActionValidationCount;
     /** The array for the timers between recovery actions. */
     private @NonNull long[] mDataStallRecoveryDelayMillisArray;
     /** The boolean array for the flags. They are used to skip the recovery actions if needed. */
@@ -253,6 +258,7 @@
      * @param phone The phone instance.
      * @param dataNetworkController Data network controller
      * @param dataServiceManager The WWAN data service manager.
+     * @param featureFlags The feature flag.
      * @param looper The looper to be used by the handler. Currently the handler thread is the phone
      *     process's main thread.
      * @param callback Callback to notify data network controller for data stall events.
@@ -261,6 +267,7 @@
             @NonNull Phone phone,
             @NonNull DataNetworkController dataNetworkController,
             @NonNull DataServiceManager dataServiceManager,
+            @NonNull FeatureFlags featureFlags,
             @NonNull Looper looper,
             @NonNull DataStallRecoveryManagerCallback callback) {
         super(looper);
@@ -269,6 +276,7 @@
         log("DataStallRecoveryManager created.");
         mDataNetworkController = dataNetworkController;
         mWwanDataServiceManager = dataServiceManager;
+        mFeatureFlags = featureFlags;
         mDataConfigManager = mDataNetworkController.getDataConfigManager();
         mDataNetworkController
                 .getDataSettingsManager()
@@ -288,7 +296,7 @@
 
         registerAllEvents();
 
-        mStats = new DataStallRecoveryStats(mPhone, dataNetworkController);
+        mStats = new DataStallRecoveryStats(mPhone, mFeatureFlags, dataNetworkController);
     }
 
     /** Register for all events that data stall monitor is interested. */
@@ -372,6 +380,7 @@
                                     0) != 0) {
                         mIsAirPlaneModeEnableDuringDataStall = true;
                     }
+                    setRecoveryAction(mLastAction);
                 }
                 break;
             case EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED:
@@ -527,6 +536,7 @@
         // during data stalled.
         if (mDataStalled && enabled) {
             mMobileDataChangedToEnabledDuringDataStall = true;
+            setRecoveryAction(mLastAction);
         }
     }
 
@@ -544,6 +554,8 @@
         mTimeLastRecoveryStartMs = 0;
         mLastAction = RECOVERY_ACTION_GET_DATA_CALL_LIST;
         mRecoveryAction = RECOVERY_ACTION_GET_DATA_CALL_LIST;
+        mValidationCount = 0;
+        mActionValidationCount = 0;
     }
 
     /**
@@ -554,8 +566,16 @@
     private void onInternetValidationStatusChanged(@ValidationStatus int status) {
         logl("onInternetValidationStatusChanged: " + DataUtils.validationStatusToString(status));
         final boolean isValid = status == NetworkAgent.VALIDATION_STATUS_VALID;
+        if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
+            mValidationCount += 1;
+            mActionValidationCount += 1;
+        }
         setNetworkValidationState(isValid);
         if (isValid) {
+            if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
+                // Broadcast intent that data stall recovered.
+                broadcastDataStallDetected(getRecoveryAction());
+            }
             reset();
         } else if (isRecoveryNeeded(true)) {
             // Set the network as invalid, because recovery is needed
@@ -594,6 +614,10 @@
      */
     @VisibleForTesting
     public void setRecoveryAction(@RecoveryAction int action) {
+        // Reset the validation count for action change
+        if (mFeatureFlags.dsrsDiagnosticsEnabled() && mRecoveryAction != action) {
+            mActionValidationCount = 0;
+        }
         mRecoveryAction = action;
 
         // Check if the mobile data enabled is TRUE, it means that the mobile data setting changed
@@ -672,13 +696,16 @@
         final boolean isRecovered = !mDataStalled;
         final int duration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
         final @RecoveredReason int reason = getRecoveredReason(mIsValidNetwork);
-        final boolean isFirstValidationOfAction = false;
         final int durationOfAction = (int) getDurationOfCurrentRecoveryMs();
+        if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
+            log("mValidationCount=" + mValidationCount
+                    + ", mActionValidationCount=" + mActionValidationCount);
+        }
 
         // Get the bundled DSRS stats.
         Bundle bundle = mStats.getDataStallRecoveryMetricsData(
-                recoveryAction, isRecovered, duration, reason, isFirstValidationOfAction,
-                durationOfAction);
+                recoveryAction, isRecovered, duration, reason, mValidationCount,
+                mActionValidationCount, durationOfAction);
 
         // Put the bundled stats extras on the intent.
         intent.putExtra("EXTRA_DSRS_STATS_BUNDLE", bundle);
@@ -781,7 +808,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 259b482..0dda7b5 100644
--- a/src/java/com/android/internal/telephony/data/DataUtils.java
+++ b/src/java/com/android/internal/telephony/data/DataUtils.java
@@ -93,6 +93,7 @@
             case "RCS":
                 return NetworkCapabilities.NET_CAPABILITY_RCS;
             default:
+                loge("Illegal network capability: " + capabilityString);
                 return -1;
         }
     }
@@ -108,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*"))
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/domainselection/DomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
index f5cf950..e3eed00 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
@@ -19,29 +19,33 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.AsyncResult;
-import android.os.CancellationSignal;
 import android.os.Handler;
 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;
@@ -49,7 +53,6 @@
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
 
 
 /**
@@ -61,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 {
@@ -73,14 +88,30 @@
         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) {
+        public void onCreated(@NonNull IDomainSelector selector) {
             synchronized (mLock) {
                 mDomainSelector = selector;
-                if (mDisposed) {
-                    mDomainSelector.cancelSelection();
+                if (checkState(STATUS_DISPOSED)) {
+                    try {
+                        selector.finishSelection();
+                    } catch (RemoteException e) {
+                        // ignore exception
+                    }
                     return;
                 }
                 DomainSelectionConnection.this.onCreated();
@@ -90,70 +121,92 @@
         @Override
         public void onWlanSelected(boolean useEmergencyPdn) {
             synchronized (mLock) {
-                if (mDisposed) return;
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
+                setState(STATUS_DOMAIN_SELECTED);
                 DomainSelectionConnection.this.onWlanSelected(useEmergencyPdn);
             }
         }
 
         @Override
-        public @NonNull WwanSelectorCallback onWwanSelected() {
+        public void onWwanSelectedAsync(@NonNull final ITransportSelectorResultCallback cb) {
             synchronized (mLock) {
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
                 if (mWwanSelectorCallback == null) {
-                    mWwanSelectorCallback = new WwanSelectorCallbackWrapper();
+                    mWwanSelectorCallback = new WwanSelectorCallbackAdaptor();
                 }
-                if (mDisposed) {
-                    return mWwanSelectorCallback;
+                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();
                 }
-                DomainSelectionConnection.this.onWwanSelected();
-                return mWwanSelectorCallback;
             }
         }
 
-        @Override
-        public void onWwanSelected(final Consumer<WwanSelectorCallback> consumer) {
+        private void onWwanSelectedAsyncInternal(
+                @NonNull final ITransportSelectorResultCallback cb) {
             synchronized (mLock) {
-                if (mDisposed) return;
-                if (mWwanSelectorCallback == null) {
-                    mWwanSelectorCallback = new WwanSelectorCallbackWrapper();
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
                 }
-                initHandler();
-                mHandler.post(() -> {
-                    synchronized (mLock) {
-                        if (mDisposed) return;
-                        DomainSelectionConnection.this.onWwanSelected();
-                        consumer.accept(mWwanSelectorCallback);
-                    }
-                });
+            }
+            DomainSelectionConnection.this.onWwanSelected();
+            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);
+                }
             }
         }
 
         @Override
         public void onSelectionTerminated(int cause) {
             synchronized (mLock) {
-                if (mDisposed) return;
+                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) {
+        public void onRequestEmergencyNetworkScan(
+                @NonNull @RadioAccessNetworkType int[] preferredNetworks,
+                @EmergencyScanType int scanType, boolean resetScan,
+                @NonNull IWwanSelectorResultCallback cb) {
             synchronized (mLock) {
-                if (mDisposed) return;
-                if (signal != null) signal.setOnCancelListener(this);
-                mResultCallback = consumer;
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
+                mResultCallback = cb;
                 initHandler();
                 mHandler.post(() -> {
                     synchronized (mLock) {
                         DomainSelectionConnection.this.onRequestEmergencyNetworkScan(
-                                preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
-                                scanType);
+                                preferredNetworks, scanType, resetScan);
                     }
                 });
             }
@@ -163,7 +216,10 @@
         public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain,
                 boolean useEmergencyPdn) {
             synchronized (mLock) {
-                if (mDisposed) return;
+                if (checkState(STATUS_DISPOSED)) {
+                    return;
+                }
+                setState(STATUS_DOMAIN_SELECTED);
                 DomainSelectionConnection.this.onDomainSelected(domain, useEmergencyPdn);
             }
         }
@@ -171,7 +227,9 @@
         @Override
         public void onCancel() {
             synchronized (mLock) {
-                if (mDisposed || mHandler == null) return;
+                if (checkState(STATUS_DISPOSED) || mHandler == null) {
+                    return;
+                }
                 mHandler.post(() -> {
                     DomainSelectionConnection.this.onCancel();
                 });
@@ -189,14 +247,22 @@
             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());
+                    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:
                     ar = (AsyncResult) msg.obj;
@@ -206,6 +272,36 @@
                     }
                     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);
                     break;
@@ -215,10 +311,9 @@
 
     protected String mTag = "DomainSelectionConnection";
 
-    private boolean mDisposed = false;
     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
@@ -229,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;
@@ -246,10 +344,13 @@
     private final @NonNull Looper mLooper;
     protected @Nullable DomainSelectionConnectionHandler mHandler;
     private boolean mRegisteredRegistrant;
-    private boolean mIsWaitingForScanResult;
 
     private @NonNull AndroidFuture<Integer> mOnComplete;
 
+    private @Nullable ScanRequest mPendingScanRequest;
+
+    private boolean mIsTestMode = false;
+
     /**
      * Creates an instance.
      *
@@ -267,7 +368,7 @@
         mIsEmergency = isEmergency;
         mLooper = Looper.getMainLooper();
 
-        mTransportSelectorCallback = new TransportSelectorCallbackWrapper();
+        mTransportSelectorCallback = new TransportSelectorCallbackAdaptor();
         mOnComplete = new AndroidFuture<>();
     }
 
@@ -281,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.
@@ -309,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);
+            }
+        }
     }
 
     /**
@@ -365,20 +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) {
-        if (mHandler == null) return;
-
+            @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);
     }
 
     /**
@@ -413,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);
         }
     }
@@ -424,12 +574,7 @@
      * to clean up all ongoing operations with the framework.
      */
     public void cancelSelection() {
-        synchronized (mLock) {
-            if (mDomainSelector != null) {
-                mDomainSelector.cancelSelection();
-            }
-            dispose();
-        }
+        finishSelection();
     }
 
     /**
@@ -440,11 +585,31 @@
      */
     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;
+            }
+        }
     }
 
     /**
@@ -452,21 +617,102 @@
      */
     public void finishSelection() {
         synchronized (mLock) {
-            if (mDomainSelector != null) {
-                mDomainSelector.finishSelection();
+            try {
+                if (mDomainSelector != null) {
+                    mDomainSelector.finishSelection();
+                }
+            } catch (RemoteException e) {
+                loge("finishSelection exception=" + e);
+            } finally {
+                dispose();
             }
-            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() {
-        mDisposed = true;
+        setState(STATUS_DISPOSED);
         if (mRegisteredRegistrant) {
             mPhone.unregisterForEmergencyNetworkScan(mHandler);
             mRegisteredRegistrant = false;
@@ -520,6 +766,28 @@
                 : 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.
      */
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 65ac8b3..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);
     }
 
@@ -171,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);
     }
 
     /**
@@ -205,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 c397788..66b977d 100644
--- a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
@@ -28,16 +28,21 @@
 
 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.PreciseDisconnectCause;
 import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CallFailCause;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.data.AccessNetworksManager;
 import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
@@ -106,7 +111,7 @@
     /** {@inheritDoc} */
     @Override
     public void onWwanSelected() {
-        mEmergencyStateTracker.onEmergencyTransportChanged(
+        mEmergencyStateTracker.onEmergencyTransportChangedAndWait(
                 EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
     }
 
@@ -159,7 +164,8 @@
     private AccessNetworksManager.AccessNetworksManagerCallback mPreferredTransportCallback =
             new AccessNetworksManager.AccessNetworksManagerCallback(Runnable::run) {
         @Override
-        public void onPreferredTransportChanged(@NetCapability int capability) {
+        public void onPreferredTransportChanged(
+                @NetCapability int capability, boolean forceReconnect) {
         }
     };
 
@@ -200,6 +206,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.
@@ -207,21 +214,57 @@
      */
     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) {
+
+        int preciseDisconnectCause = callFailCause;
+        switch (callFailCause) {
+            case CallFailCause.IMS_EMERGENCY_TEMP_FAILURE:
+                preciseDisconnectCause = PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE;
+                break;
+            case CallFailCause.IMS_EMERGENCY_PERM_FAILURE:
+                preciseDisconnectCause = PreciseDisconnectCause.EMERGENCY_PERM_FAILURE;
+                break;
+            default:
+                break;
+        }
+
         DomainSelectionService.SelectionAttributes.Builder builder =
                 new DomainSelectionService.SelectionAttributes.Builder(
                         slotId, subId, SELECTOR_TYPE_CALLING)
                 .setEmergency(true)
+                .setTestEmergencyNumber(isTest)
                 .setExitedFromAirplaneMode(exited)
                 .setCallId(callId)
-                .setNumber(number)
-                .setCsDisconnectCause(callFailCause);
+                .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null))
+                .setCsDisconnectCause(preciseDisconnectCause);
 
         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/NormalCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
index 0532a05..0fd9201 100644
--- a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
@@ -20,6 +20,8 @@
 
 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;
@@ -78,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.
     }
@@ -119,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);
 
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/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
index e2418c5..02dd613 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -16,10 +16,12 @@
 
 package com.android.internal.telephony.emergency;
 
+import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Environment;
@@ -48,6 +50,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.EmergencyNumberStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.PersistAtomsProto;
@@ -102,6 +105,7 @@
 
     private final CommandsInterface mCi;
     private final Phone mPhone;
+    private final @NonNull FeatureFlags mFeatureFlags;
     private int mPhoneId;
     private String mCountryIso;
     private String mLastKnownEmergencyCountryIso = "";
@@ -173,10 +177,20 @@
         }
     };
 
-    public EmergencyNumberTracker(Phone phone, CommandsInterface ci) {
+    public EmergencyNumberTracker(Phone phone, CommandsInterface ci,
+            @NonNull FeatureFlags featureFlags) {
+        Context ctx = phone.getContext();
+
         mPhone = phone;
         mCi = ci;
-        mResources = mPhone.getContext().getResources();
+        mFeatureFlags = featureFlags;
+        mResources = ctx.getResources();
+
+        if (mFeatureFlags.minimalTelephonyCdmCheck()
+                && !ctx.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_TELEPHONY_CALLING)) {
+            throw new UnsupportedOperationException("EmergencyNumberTracker requires calling");
+        }
 
         if (mPhone != null) {
             mPhoneId = phone.getPhoneId();
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index 0692f7d..c4d5355 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -35,6 +35,7 @@
 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;
@@ -43,14 +44,11 @@
 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.data.ApnSetting;
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -70,7 +68,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -84,12 +81,15 @@
      * 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)
@@ -117,7 +117,7 @@
     @EmergencyConstants.EmergencyMode
     private int mEmergencyMode = MODE_EMERGENCY_NONE;
     private boolean mWasEmergencyModeSetOnModem;
-    private EmergencyRegResult mLastEmergencyRegResult;
+    private EmergencyRegistrationResult mLastEmergencyRegistrationResult;
     private boolean mIsEmergencyModeInProgress;
     private boolean mIsEmergencyCallStartedDuringEmergencySms;
 
@@ -126,13 +126,10 @@
     // 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 Phone mPhoneToExit;
-    private int mPdnDisconnectionTimeoutMs = DEFAULT_EPDN_DISCONNECTION_TIMEOUT_MS;
-    private final Object mLock = new Object();
+    private final Set<android.telecom.Connection> mActiveEmergencyCalls = new ArraySet<>();
     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;
@@ -147,6 +144,11 @@
     private Phone mSmsPhone;
     private CompletableFuture<Integer> mSmsEmergencyModeFuture;
     private boolean mIsTestEmergencyNumberForSms;
+    // For tracking the emergency SMS callback mode.
+    private boolean mIsInScbm;
+    private boolean mIsEmergencySmsStartedDuringScbm;
+
+    private CompletableFuture<Boolean> mEmergencyTransportChangedFuture;
 
     private final android.util.ArrayMap<Integer, Boolean> mNoSimEcbmSupported =
             new android.util.ArrayMap<>();
@@ -175,29 +177,6 @@
         }
     };
 
-    /**
-     * 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 {
@@ -221,8 +200,6 @@
     @VisibleForTesting
     public interface TelephonyManagerProxy {
         int getPhoneCount();
-        void registerTelephonyCallback(int subId, Executor executor, TelephonyCallback callback);
-        void unregisterTelephonyCallback(TelephonyCallback callback);
     }
 
     private final TelephonyManagerProxy mTelephonyManagerProxy;
@@ -238,18 +215,6 @@
         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);
-        }
     }
 
     /**
@@ -261,15 +226,13 @@
     }
 
     @VisibleForTesting
-    public static final int MSG_SET_EMERGENCY_MODE = 1;
+    public static final int MSG_SET_EMERGENCY_MODE_DONE = 1;
     @VisibleForTesting
-    public static final int MSG_EXIT_EMERGENCY_MODE = 2;
+    public static final int MSG_EXIT_EMERGENCY_MODE_DONE = 2;
     @VisibleForTesting
-    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;
+    public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 3;
+    /** A message which is used to automatically exit from SCBM after a period of time. */
+    private static final int MSG_EXIT_SCBM = 4;
 
     private class MyHandler extends Handler {
 
@@ -286,14 +249,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);
@@ -301,38 +268,46 @@
                         // Case 1) When the emergency call is setting the emergency mode and
                         // the emergency SMS is being sent, completes the SMS future also.
                         // Case 2) When the emergency SMS is setting the emergency mode and
-                        // the emergency call is beint started, the SMS request is cancelled and
+                        // the emergency call is being started, the SMS request is cancelled and
                         // the call request will be handled.
                         if (mSmsPhone != null) {
                             completeEmergencyMode(EMERGENCY_TYPE_SMS);
                         }
                     } else if (emergencyType == EMERGENCY_TYPE_SMS) {
                         if (mPhone != null && mSmsPhone != null) {
-                            // Clear call phone temporarily to exit the emergency mode
-                            // if the emergency call is started.
                             if (mIsEmergencyCallStartedDuringEmergencySms) {
-                                Phone phone = mPhone;
-                                mPhone = null;
-                                exitEmergencyMode(mSmsPhone, emergencyType, false);
-                                // Restore call phone for further use.
-                                mPhone = phone;
-
-                                if (!isSamePhone(mPhone, mSmsPhone)) {
-                                    completeEmergencyMode(emergencyType,
-                                            DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED);
+                                if (!isSamePhone(mPhone, mSmsPhone) || !isInScbm()) {
+                                    // Clear call phone temporarily to exit the emergency mode
+                                    // if the emergency call is started.
+                                    Phone phone = mPhone;
+                                    mPhone = null;
+                                    exitEmergencyMode(mSmsPhone, emergencyType);
+                                    // Restore call phone for further use.
+                                    mPhone = phone;
+                                    if (!isSamePhone(mPhone, mSmsPhone)) {
+                                        completeEmergencyMode(emergencyType,
+                                                DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED);
+                                        exitEmergencySmsCallbackMode();
+                                    }
+                                } else {
+                                    completeEmergencyMode(emergencyType);
+                                    mIsEmergencyCallStartedDuringEmergencySms = false;
+                                    exitEmergencySmsCallbackMode();
+                                    turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
+                                            mIsTestEmergencyNumber);
                                 }
                             } else {
                                 completeEmergencyMode(emergencyType);
                             }
-                            break;
                         } else {
                             completeEmergencyMode(emergencyType);
-                        }
 
-                        if (mIsEmergencyCallStartedDuringEmergencySms) {
-                            mIsEmergencyCallStartedDuringEmergencySms = false;
-                            turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
-                                    mIsTestEmergencyNumber);
+                            if (mIsEmergencyCallStartedDuringEmergencySms) {
+                                mIsEmergencyCallStartedDuringEmergencySms = false;
+                                exitEmergencySmsCallbackMode();
+                                turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
+                                        mIsTestEmergencyNumber);
+                            }
                         }
                     }
                     break;
@@ -355,6 +330,10 @@
                             mIsEmergencyCallStartedDuringEmergencySms = false;
                             turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
                                     mIsTestEmergencyNumber);
+                        } else if (mIsEmergencySmsStartedDuringScbm) {
+                            mIsEmergencySmsStartedDuringScbm = false;
+                            setEmergencyMode(mSmsPhone, emergencyType,
+                                    MODE_EMERGENCY_WWAN, MSG_SET_EMERGENCY_MODE_DONE);
                         }
                     }
                     break;
@@ -367,30 +346,35 @@
                     setEmergencyModeInProgress(false);
                     // When the emergency callback mode is in progress and the emergency SMS is
                     // started, it needs to be completed here for the emergency SMS.
-                    if (mSmsPhone != null) {
-                        completeEmergencyMode(EMERGENCY_TYPE_SMS);
+                    if (emergencyType == EMERGENCY_TYPE_CALL) {
+                        if (mSmsPhone != null) {
+                            completeEmergencyMode(EMERGENCY_TYPE_SMS);
+                        }
+                    } else if (emergencyType == EMERGENCY_TYPE_SMS) {
+                        // When the emergency SMS callback mode is in progress on other phone and
+                        // the emergency call was started, needs to exit the emergency mode first.
+                        if (mIsEmergencyCallStartedDuringEmergencySms) {
+                            final Phone smsPhone = mSmsPhone;
+                            exitEmergencySmsCallbackMode();
+
+                            if (mPhone != null && smsPhone != null
+                                    && !isSamePhone(mPhone, smsPhone)) {
+                                Phone phone = mPhone;
+                                mPhone = null;
+                                exitEmergencyMode(smsPhone, emergencyType);
+                                // Restore call phone for further use.
+                                mPhone = phone;
+                            } else {
+                                mIsEmergencyCallStartedDuringEmergencySms = false;
+                                turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
+                                        mIsTestEmergencyNumber);
+                            }
+                        }
                     }
                     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));
-                    }
+                case MSG_EXIT_SCBM: {
+                    exitEmergencySmsCallbackModeAndEmergencyMode();
                     break;
                 }
                 default:
@@ -496,22 +480,41 @@
      * 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;
+                exitEmergencySmsCallbackMode();
+                mOngoingConnection = c;
                 mIsTestEmergencyNumber = isTestEmergencyNumber;
+                if (isInEcm()) {
+                    // Remove pending exit ECM runnable.
+                    mHandler.removeCallbacks(mExitEcmRunnable);
+                    releaseWakeLock();
+                    ((GsmCdmaPhone) mPhone).notifyEcbmTimerReset(Boolean.TRUE);
+
+                    mOngoingCallProperties = 0;
+                    mCallEmergencyModeFuture = new CompletableFuture<>();
+                    setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN,
+                            MSG_SET_EMERGENCY_MODE_DONE);
+                    return mCallEmergencyModeFuture;
+                }
+                // Ensure that domain selector requests scan.
+                mLastEmergencyRegistrationResult = new EmergencyRegistrationResult(
+                        AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+                        NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
+                        NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
                 return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
             }
 
@@ -530,17 +533,29 @@
             // 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, false);
+                if (!isSamePhone(mSmsPhone, phone)) {
+                    exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                } else {
+                    // If the device is already in the emergency mode on the same phone,
+                    // the general emergency call procedure can be immediately performed.
+                    // And, if the emergency PDN is already connected, then we need to keep
+                    // this PDN active while initating the emergency call.
+                    mIsEmergencyCallStartedDuringEmergencySms = false;
+                }
+
+                exitEmergencySmsCallbackMode();
             }
 
-            mPhone = phone;
-            mOngoingCallId = callId;
-            mIsTestEmergencyNumber = isTestEmergencyNumber;
-            return mCallEmergencyModeFuture;
+            if (mIsEmergencyCallStartedDuringEmergencySms) {
+                mPhone = phone;
+                mOngoingConnection = c;
+                mIsTestEmergencyNumber = isTestEmergencyNumber;
+                return mCallEmergencyModeFuture;
+            }
         }
 
         mPhone = phone;
-        mOngoingCallId = callId;
+        mOngoingConnection = c;
         mIsTestEmergencyNumber = isTestEmergencyNumber;
         turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber);
         return mCallEmergencyModeFuture;
@@ -553,39 +568,46 @@
      * 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()) {
+                && isEmergencyCallbackModeSupported(mPhone)) {
             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, false);
+                if (isInScbm()) {
+                    setIsInEmergencyCall(false);
+                    setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                } else {
+                    exitEmergencyMode(mPhone, EMERGENCY_TYPE_CALL);
+                }
                 clearEmergencyCallInfo();
             }
         }
+
+        // Release any blocked thread immediately
+        maybeNotifyTransportChangeCompleted(EMERGENCY_TYPE_CALL, true);
     }
 
     private void clearEmergencyCallInfo() {
@@ -593,7 +615,7 @@
         mIsTestEmergencyNumber = false;
         mIsEmergencyCallStartedDuringEmergencySms = false;
         mCallEmergencyModeFuture = null;
-        mOngoingCallId = null;
+        mOngoingConnection = null;
         mOngoingCallProperties = 0;
         mPhone = null;
     }
@@ -606,12 +628,24 @@
                 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.
-            setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_WWAN,
-                    MSG_SET_EMERGENCY_MODE_DONE);
+            // 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.
+            if (mEmergencyMode != MODE_EMERGENCY_WWAN) {
+                setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_WWAN,
+                        MSG_SET_EMERGENCY_MODE_DONE);
+            } else {
+                // Ensure that domain selector requests the network scan.
+                mLastEmergencyRegistrationResult = new EmergencyRegistrationResult(
+                        AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+                        NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
+                        NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
+                if (emergencyType == EMERGENCY_TYPE_CALL) {
+                    setIsInEmergencyCall(true);
+                }
+                completeEmergencyMode(emergencyType);
+            }
         });
     }
 
@@ -629,14 +663,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
@@ -647,26 +682,33 @@
             return;
         }
 
-        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);
+    }
+
+    /**
+     * Sets the emergency callback mode on modem.
+     *
+     * @param phone the {@code Phone} to set the emergency mode on modem.
+     * @param emergencyType the emergency type to identify an emergency call or SMS.
+     */
+    private void setEmergencyCallbackMode(Phone phone, @EmergencyType int emergencyType) {
+        boolean needToSetCallbackMode = false;
+
+        if (emergencyType == EMERGENCY_TYPE_CALL) {
+            needToSetCallbackMode = true;
+        } else if (emergencyType == EMERGENCY_TYPE_SMS) {
+            // Ensure that no emergency call is in progress.
+            if (mActiveEmergencyCalls.isEmpty() && mOngoingConnection == null
+                    && mOngoingEmergencySmsIds.isEmpty()) {
+                needToSetCallbackMode = true;
             }
-            mWasEmergencyModeSetOnModem = true;
-            phone.setEmergencyMode(mode, m);
+        }
+
+        if (needToSetCallbackMode) {
+            // Set emergency mode on modem.
+            setEmergencyMode(phone, emergencyType, MODE_EMERGENCY_CALLBACK,
+                    MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
         }
     }
 
@@ -740,10 +782,8 @@
      *
      * @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,
-            boolean waitForPdnDisconnect) {
+    private void exitEmergencyMode(Phone phone, @EmergencyType int emergencyType) {
         Rlog.i(TAG, "exitEmergencyMode for " + emergencyTypeToString(emergencyType));
 
         if (emergencyType == EMERGENCY_TYPE_CALL) {
@@ -779,28 +819,76 @@
             return;
         }
 
-        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);
+        mWasEmergencyModeSetOnModem = false;
+        phone.exitEmergencyMode(m);
+    }
+
+    /** 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;
                 }
-                return;
-            } else {
-                unregisterForDataConnectionStateChanges();
-                mPhoneToExit = null;
+                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();
+                }
             }
-            phone.exitEmergencyMode(m);
         }
     }
 
-    /** Returns last {@link EmergencyRegResult} as set by {@code setEmergencyMode()}. */
-    public EmergencyRegResult getEmergencyRegResult() {
-        return mLastEmergencyRegResult;
+    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);
     }
 
     /**
@@ -832,10 +920,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):
@@ -863,13 +951,13 @@
      * 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);
-            if (Objects.equals(mOngoingCallId, callId)) {
-                Rlog.i(TAG, "call connected " + 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) {
@@ -884,10 +972,10 @@
      * Handles the change of emergency call properties.
      *
      * @param properties the new call properties.
-     * @param callId the callId whose state has changed.
+     * @param c the call whose state has changed.
      */
-    public void onEmergencyCallPropertiesChanged(int properties, String callId) {
-        if (Objects.equals(mOngoingCallId, callId)) {
+    public void onEmergencyCallPropertiesChanged(int properties, android.telecom.Connection c) {
+        if (Objects.equals(mOngoingConnection, c)) {
             mOngoingCallProperties = properties;
         }
     }
@@ -896,12 +984,8 @@
      * Handles the radio power off request.
      */
     public void onCellularRadioPowerOffRequested() {
-        synchronized (mLock) {
-            if (isInEcm()) {
-                exitEmergencyCallbackMode(null);
-            }
-            exitEmergencyModeIfDelayed();
-        }
+        exitEmergencySmsCallbackModeAndEmergencyMode();
+        exitEmergencyCallbackMode();
     }
 
     private static boolean isVoWiFi(int properties) {
@@ -911,14 +995,16 @@
 
     /**
      * Returns {@code true} if device and carrier support emergency callback mode.
+     *
+     * @param phone The {@link Phone} instance to be checked.
      */
     @VisibleForTesting
-    public boolean isEmergencyCallbackModeSupported() {
-        int subId = mPhone.getSubId();
+    public boolean isEmergencyCallbackModeSupported(Phone phone) {
+        int subId = phone.getSubId();
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             // If there is no SIM, refer to the saved last carrier configuration with valid
             // subscription.
-            int phoneId = mPhone.getPhoneId();
+            int phoneId = phone.getPhoneId();
             Boolean savedConfig = mNoSimEcbmSupported.get(Integer.valueOf(phoneId));
             if (savedConfig == null) {
                 // Exceptional case such as with poor boot performance.
@@ -960,20 +1046,21 @@
                 // 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);
         }
+
+        setEmergencyCallbackMode(mPhone, EMERGENCY_TYPE_CALL);
+
+        // Post this runnable so we will automatically exit if no one invokes
+        // exitEmergencyCallbackMode() directly.
+        long delayInMillis = TelephonyProperties.ecm_exit_timer()
+                .orElse(mEcmExitTimeoutMs);
+        mHandler.postDelayed(mExitEcmRunnable, delayInMillis);
+
+        // We don't want to go to sleep while in ECM.
+        if (mWakeLock != null) mWakeLock.acquire(delayInMillis);
     }
 
     /**
@@ -991,14 +1078,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.
@@ -1006,9 +1086,7 @@
             gsmCdmaPhone.notifyEmergencyCallRegistrants(false);
 
             // Exit emergency mode on modem.
-            // b/299866883: Wait for the disconnection of ePDN before calling exitEmergencyMode.
-            exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL,
-                    mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_PS);
+            exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL);
         }
 
         mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN;
@@ -1016,6 +1094,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.
      */
@@ -1085,7 +1175,8 @@
      */
     public CompletableFuture<Integer> startEmergencySms(@NonNull Phone phone, @NonNull String smsId,
             boolean isTestEmergencyNumber) {
-        Rlog.i(TAG, "startEmergencySms: phoneId=" + phone.getPhoneId() + ", smsId=" + smsId);
+        Rlog.i(TAG, "startEmergencySms: phoneId=" + phone.getPhoneId() + ", smsId=" + smsId
+                + ", scbm=" + isInScbm());
 
         // When an emergency call is in progress, it checks whether an emergency call is already in
         // progress on the different phone.
@@ -1094,17 +1185,29 @@
             return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
         }
 
-        // When an emergency SMS is in progress, it checks whether an emergency SMS is already in
-        // progress on the different phone.
+        boolean exitScbmInOtherPhone = false;
+        boolean smsStartedInScbm = isInScbm();
+
+        // When an emergency SMS is in progress, it checks whether an emergency SMS is already
+        // in progress on the different phone.
         if (mSmsPhone != null && !isSamePhone(mSmsPhone, phone)) {
-            Rlog.e(TAG, "Emergency SMS is in progress on the other slot.");
-            return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
+            if (smsStartedInScbm) {
+                // When other phone is in the emergency SMS callback mode, we need to stop the
+                // emergency SMS callback mode first.
+                exitScbmInOtherPhone = true;
+                mIsEmergencySmsStartedDuringScbm = true;
+                exitEmergencySmsCallbackModeAndEmergencyMode();
+            } else {
+                Rlog.e(TAG, "Emergency SMS is in progress on the other slot.");
+                return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
+            }
         }
 
-        // When the previous emergency SMS is not completed yet,
+        // When the previous emergency SMS is not completed yet and the device is not in SCBM,
         // this new request will not be allowed.
-        if (mSmsPhone != null && isInEmergencyMode() && isEmergencyModeInProgress()) {
-            Rlog.e(TAG, "Existing emergency SMS is in progress.");
+        if (mSmsPhone != null && isInEmergencyMode() && isEmergencyModeInProgress()
+                && !smsStartedInScbm) {
+            Rlog.e(TAG, "Existing emergency SMS is in progress and not in SCBM.");
             return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
         }
 
@@ -1112,17 +1215,31 @@
         mIsTestEmergencyNumberForSms = isTestEmergencyNumber;
         mOngoingEmergencySmsIds.add(smsId);
 
-        // When the emergency mode is already set by the previous emergency call or SMS,
-        // completes the future immediately.
-        if (isInEmergencyMode() && !isEmergencyModeInProgress()) {
-            return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
+        if (smsStartedInScbm) {
+            // When the device is in SCBM and emergency SMS is being sent,
+            // completes the future immediately.
+            if (!exitScbmInOtherPhone) {
+                // The emergency SMS is allowed and returns the success result.
+                return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
+            }
+
+            mSmsEmergencyModeFuture = new CompletableFuture<>();
+        } else {
+            // When the emergency mode is already set by the previous emergency call or SMS,
+            // completes the future immediately.
+            if (isInEmergencyMode() && !isEmergencyModeInProgress()) {
+                // The emergency SMS is allowed and returns the success result.
+                return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
+            }
+
+            mSmsEmergencyModeFuture = new CompletableFuture<>();
+
+            if (!isInEmergencyMode()) {
+                setEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN,
+                        MSG_SET_EMERGENCY_MODE_DONE);
+            }
         }
 
-        mSmsEmergencyModeFuture = new CompletableFuture<>();
-        if (!isInEmergencyMode()) {
-            setEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN,
-                    MSG_SET_EMERGENCY_MODE_DONE);
-        }
         return mSmsEmergencyModeFuture;
     }
 
@@ -1133,35 +1250,142 @@
      * @param smsId the SMS id on which to end 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.
+     * @param domain the domain that MO SMS was sent.
      */
-    public void endSms(@NonNull String smsId, boolean success) {
+    public void endSms(@NonNull String smsId, boolean success,
+            @NetworkRegistrationInfo.Domain int domain) {
         mOngoingEmergencySmsIds.remove(smsId);
 
         // If the outgoing emergency SMSs are empty, we can try to exit the emergency mode.
         if (mOngoingEmergencySmsIds.isEmpty()) {
+            mSmsEmergencyModeFuture = null;
+            mIsEmergencySmsStartedDuringScbm = false;
+
             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) {
-                    setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_CALLBACK,
-                            MSG_SET_EMERGENCY_CALLBACK_MODE_DONE);
+                if (mActiveEmergencyCalls.isEmpty() && mOngoingConnection == null) {
+                    setEmergencyCallbackMode(mPhone, EMERGENCY_TYPE_CALL);
                 }
-            } else {
-                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, false);
             }
 
-            clearEmergencySmsInfo();
+            // If SCBM supports, SCBM will be entered here regardless of ECBM state.
+            if (success && domain == NetworkRegistrationInfo.DOMAIN_PS
+                    && (isInScbm() || isEmergencyCallbackModeSupported(mSmsPhone))) {
+                enterEmergencySmsCallbackMode();
+            } else if (isInScbm()) {
+                // Sets the emergency mode to CALLBACK without re-initiating SCBM timer.
+                setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+            } else {
+                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                clearEmergencySmsInfo();
+            }
+        }
+    }
+
+    /**
+     * Called when emergency SMS is received from the network.
+     */
+    public void onEmergencySmsReceived() {
+        if (isInScbm()) {
+            Rlog.d(TAG, "Emergency SMS received, re-initiate SCBM timer");
+            // Reinitiate the SCBM timer when receiving emergency SMS while in SCBM.
+            enterEmergencySmsCallbackMode();
         }
     }
 
     private void clearEmergencySmsInfo() {
         mOngoingEmergencySmsIds.clear();
+        mIsEmergencySmsStartedDuringScbm = false;
         mIsTestEmergencyNumberForSms = false;
         mSmsEmergencyModeFuture = null;
         mSmsPhone = null;
     }
 
     /**
+     * Returns {@code true} if currently in emergency SMS callback mode.
+     */
+    public boolean isInScbm() {
+        return mIsInScbm;
+    }
+
+    /**
+     * Sets the emergency SMS callback mode state.
+     *
+     * @param isInScbm {@code true} if currently in emergency SMS callback mode,
+     *                 {@code false} otherwise.
+     */
+    private void setIsInScbm(boolean isInScbm) {
+        mIsInScbm = isInScbm;
+    }
+
+    /**
+     * Enters the emergency SMS callback mode.
+     */
+    private void enterEmergencySmsCallbackMode() {
+        Rlog.d(TAG, "enter SCBM while " + (isInScbm() ? "in" : "not in") + " SCBM");
+        // Remove pending message if present.
+        mHandler.removeMessages(MSG_EXIT_SCBM);
+
+        if (!isInScbm()) {
+            setIsInScbm(true);
+        }
+
+        setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+
+        // At the moment, the default SCBM timer value will be used with the same value
+        // that is configured for emergency callback mode.
+        int delayInMillis = Long.valueOf(mEcmExitTimeoutMs).intValue();
+        int subId = mSmsPhone.getSubId();
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            delayInMillis = getConfig(subId,
+                    CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT,
+                    delayInMillis);
+            if (delayInMillis == 0) {
+                delayInMillis = Long.valueOf(mEcmExitTimeoutMs).intValue();
+            }
+        }
+        // Post the message so we will automatically exit if no one invokes
+        // exitEmergencySmsCallbackModeAndEmergencyMode() directly.
+        mHandler.sendEmptyMessageDelayed(MSG_EXIT_SCBM, delayInMillis);
+    }
+
+    /**
+     * Exits emergency SMS callback mode and emergency mode if the device is in SCBM and
+     * the emergency mode is in CALLBACK.
+     */
+    private void exitEmergencySmsCallbackModeAndEmergencyMode() {
+        Rlog.d(TAG, "exit SCBM and emergency mode");
+        final Phone smsPhone = mSmsPhone;
+        boolean wasInScbm = isInScbm();
+        exitEmergencySmsCallbackMode();
+
+        // The emergency mode needs to be checked to ensure that there is no ongoing emergency SMS.
+        if (wasInScbm && mOngoingEmergencySmsIds.isEmpty()) {
+            // Exit emergency mode on modem.
+            exitEmergencyMode(smsPhone, EMERGENCY_TYPE_SMS);
+        }
+    }
+
+    /**
+     * Exits emergency SMS callback mode.
+     */
+    private void exitEmergencySmsCallbackMode() {
+        // Remove pending message if present.
+        mHandler.removeMessages(MSG_EXIT_SCBM);
+
+        if (isInScbm()) {
+            Rlog.i(TAG, "exit SCBM");
+            setIsInScbm(false);
+        }
+
+        if (mOngoingEmergencySmsIds.isEmpty()) {
+            mIsTestEmergencyNumberForSms = false;
+            mSmsPhone = null;
+        }
+    }
+
+    /**
      * Returns {@code true} if any phones from PhoneFactory have radio on.
      */
     private boolean isRadioOn() {
@@ -1185,8 +1409,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.
      *
@@ -1209,6 +1433,11 @@
                 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) {
@@ -1223,25 +1452,34 @@
                             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.
+                    // 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);
         }
@@ -1394,6 +1632,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.
      */
@@ -1464,49 +1723,4 @@
         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..4e773f3 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,18 @@
         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);
+        /**
+         * Called when the connected LPA does not implement
+         * EuiccService#onGetAvailableMemoryInBytes(int).
+         */
+        void onUnsupportedOperationExceptionComplete(String message);
+    }
+
     /** Callback class for {@link #getOtaStatus}. */
     @VisibleForTesting(visibility = PACKAGE)
     public interface GetOtaStatusCommandCallback extends BaseEuiccCommandCallback {
@@ -436,6 +450,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 +781,34 @@
                                     });
                             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);
+                                            });
+                                        }
+
+                                        @Override
+                                        public void onUnsupportedOperationException(
+                                                String message) {
+                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
+                                                ((GetAvailableMemoryInBytesCommandCallback)
+                                                        callback)
+                                                        .onUnsupportedOperationExceptionComplete(
+                                                            message);
+                                                onCommandEnd(callback);
+                                            });
+                                        }
+                                    });
+                            break;
+                        }
                         case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: {
                             GetMetadataRequest request = (GetMetadataRequest) message.obj;
                             mEuiccService.getDownloadableSubscriptionMetadata(slotId,
@@ -1036,6 +1085,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..18b4b14 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;
             }
@@ -1235,8 +1331,13 @@
                     SubscriptionInfo subscriptionInfo =
                               mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(
                                     slot.getPhoneIdFromPortIndex(portIndex));
-                    if (subscriptionInfo == null || subscriptionInfo.isOpportunistic()) {
-                            // If the port is active and empty/opportunistic, return the portIndex.
+                    if (subscriptionInfo == null
+                        || subscriptionInfo.isOpportunistic()
+                        || (mFeatureFlags.esimBootstrapProvisioningFlag()
+                            && subscriptionInfo.getProfileClass()
+                            == SubscriptionManager.PROFILE_CLASS_PROVISIONING)) {
+                            // If the port is active and has empty/opportunistic/provisioning
+                            // profiles then return the portIndex.
                         return portIndex;
                     }
                 }
@@ -1604,9 +1705,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 +1857,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 +1893,42 @@
         return awaitResult(latch, eidRef);
     }
 
+    private long blockingGetAvailableMemoryInBytesFromEuiccService(int cardId)
+            throws UnsupportedOperationException {
+        CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<Long> memoryRef =
+                new AtomicReference<>(EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE);
+        AtomicReference<Exception> exceptionRef = new AtomicReference();
+        mConnector.getAvailableMemoryInBytes(
+                cardId,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        memoryRef.set(availableMemoryInBytes);
+                        latch.countDown();
+                    }
+
+                    @Override
+                    public void onUnsupportedOperationExceptionComplete(String message) {
+                        exceptionRef.set(new UnsupportedOperationException(message));
+                        latch.countDown();
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        latch.countDown();
+                    }
+                });
+        try {
+            return awaitResultOrException(latch, memoryRef, exceptionRef);
+        } catch (UnsupportedOperationException uoe) {
+            throw uoe;
+        } catch (Exception e) {
+            // Other type of exceptions are not expected here but re-throw in case that happens.
+            throw new UnsupportedOperationException(e);
+        }
+    }
+
     private @OtaStatus int blockingGetOtaStatusFromEuiccService(int cardId) {
         CountDownLatch latch = new CountDownLatch(1);
         AtomicReference<Integer> statusRef =
@@ -1788,6 +1976,24 @@
         return resultRef.get();
     }
 
+    private static <T> T awaitResultOrException(
+            CountDownLatch latch,
+            AtomicReference<T> resultRef,
+            AtomicReference<Exception> resultException)
+            throws Exception {
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+
+        if (resultException.get() != null) {
+            throw resultException.get();
+        }
+
+        return resultRef.get();
+    }
+
     // Returns whether the caller has carrier privilege on the given subscription.
     private boolean checkCarrierPrivilegeInMetadata(DownloadableSubscription subscription,
             String callingPackage) {
@@ -1948,15 +2154,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 +2309,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 dae808a..e5afbeb 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -171,7 +171,7 @@
             if(mPhone.getServiceState().getRilDataRadioTechnology()
                     != ServiceState.RIL_RADIO_TECHNOLOGY_NR) {
                 tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
-                notifySmsSentFailedToEmergencyStateTracker(tracker);
+                notifySmsSentFailedToEmergencyStateTracker(tracker, false);
                 return;
             }
         }
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 b21d45d..a6bb1d6 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -16,15 +16,16 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
 import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE;
 import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE;
 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED;
 import static android.telephony.ims.RegistrationManager.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.RegistrationManager.SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
 
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC;
@@ -49,7 +50,6 @@
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.Notification;
@@ -124,7 +124,6 @@
 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.flags.Flags;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.metrics.ImsStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
@@ -290,6 +289,8 @@
 
     private final RegistrantList mSilentRedialRegistrants = new RegistrantList();
 
+    private final RegistrantList mImsRegistrationUpdateRegistrants = new RegistrantList();
+
     private final LocalLog mRegLocalLog = new LocalLog(64);
     private TelephonyMetrics mMetrics;
 
@@ -313,6 +314,7 @@
     private @RegistrationManager.SuggestedAction int mImsRegistrationSuggestedAction;
     private @ImsRegistrationImplBase.ImsRegistrationTech int mImsDeregistrationTech =
             REGISTRATION_TECH_NONE;
+    private @AccessNetworkConstants.TransportType int mTransportType = TRANSPORT_TYPE_INVALID;
     private int mImsRegistrationCapabilities;
     private boolean mNotifiedRegisteredState;
 
@@ -1664,6 +1666,14 @@
         }
     }
 
+    public void registerForImsRegistrationChanges(Handler h, int what, Object obj) {
+        mImsRegistrationUpdateRegistrants.addUnique(h, what, obj);
+    }
+
+    public void unregisterForImsRegistrationChanges(Handler h) {
+        mImsRegistrationUpdateRegistrants.remove(h);
+    }
+
     @Override
     public void registerForSilentRedial(Handler h, int what, Object obj) {
         mSilentRedialRegistrants.addUnique(h, what, obj);
@@ -2436,7 +2446,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
@@ -2445,6 +2457,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);
@@ -2465,7 +2480,7 @@
         int subId = getSubId();
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
             updateImsRegistrationInfo(REGISTRATION_STATE_NOT_REGISTERED,
-                    REGISTRATION_TECH_NONE, SUGGESTED_ACTION_NONE);
+                    REGISTRATION_TECH_NONE, SUGGESTED_ACTION_NONE, TRANSPORT_TYPE_INVALID);
         }
     }
 
@@ -2473,13 +2488,13 @@
             ImsRegistrationCallbackHelper.ImsRegistrationUpdate() {
         @Override
         public void handleImsRegistered(@NonNull ImsRegistrationAttributes attributes) {
-            int imsRadioTech = attributes.getTransportType();
+            int imsTransportType = attributes.getTransportType();
             if (DBG) {
-                logd("handleImsRegistered: onImsMmTelConnected imsRadioTech="
-                        + AccessNetworkConstants.transportTypeToString(imsRadioTech));
+                logd("handleImsRegistered: onImsMmTelConnected imsTransportType="
+                        + AccessNetworkConstants.transportTypeToString(imsTransportType));
             }
-            mRegLocalLog.log("handleImsRegistered: onImsMmTelConnected imsRadioTech="
-                    + AccessNetworkConstants.transportTypeToString(imsRadioTech));
+            mRegLocalLog.log("handleImsRegistered: onImsMmTelConnected imsTransportType="
+                    + AccessNetworkConstants.transportTypeToString(imsTransportType));
             setServiceState(ServiceState.STATE_IN_SERVICE);
             getDefaultPhone().setImsRegistrationState(true);
             mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.CONNECTED, null);
@@ -2487,7 +2502,10 @@
             mImsNrSaModeHandler.onImsRegistered(
                     attributes.getRegistrationTechnology(), attributes.getFeatureTags());
             updateImsRegistrationInfo(REGISTRATION_STATE_REGISTERED,
-                    attributes.getRegistrationTechnology(), SUGGESTED_ACTION_NONE);
+                    attributes.getRegistrationTechnology(), SUGGESTED_ACTION_NONE,
+                    imsTransportType);
+            AsyncResult ar = new AsyncResult(null, null, null);
+            mImsRegistrationUpdateRegistrants.notifyRegistrants(ar);
         }
 
         @Override
@@ -2503,6 +2521,8 @@
             mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.PROGRESSING,
                     null);
             mImsStats.onImsRegistering(imsRadioTech);
+            AsyncResult ar = new AsyncResult(null, null, null);
+            mImsRegistrationUpdateRegistrants.notifyRegistrants(ar);
         }
 
         @Override
@@ -2531,19 +2551,21 @@
                     suggestedModemAction = suggestedAction;
                 } else if (mFeatureFlags.addRatRelatedSuggestedActionToImsRegistration()) {
                     if ((suggestedAction == SUGGESTED_ACTION_TRIGGER_RAT_BLOCK)
-                            || (suggestedAction == SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK)) {
+                            || (suggestedAction == SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS)) {
                         suggestedModemAction = suggestedAction;
                     }
                 }
             }
             updateImsRegistrationInfo(REGISTRATION_STATE_NOT_REGISTERED,
-                    imsRadioTech, suggestedModemAction);
+                    imsRadioTech, suggestedModemAction, TRANSPORT_TYPE_INVALID);
 
             if (mFeatureFlags.clearCachedImsPhoneNumberWhenDeviceLostImsRegistration()) {
                 // Clear the phone number from P-Associated-Uri
                 setCurrentSubscriberUris(null);
                 clearPhoneNumberForSourceIms();
             }
+            AsyncResult ar = new AsyncResult(null, null, null);
+            mImsRegistrationUpdateRegistrants.notifyRegistrants(ar);
         }
 
         @Override
@@ -2556,7 +2578,6 @@
 
     /** Clear the IMS phone number from IMS associated Uris when IMS registration is lost. */
     @VisibleForTesting
-    @FlaggedApi(Flags.FLAG_CLEAR_CACHED_IMS_PHONE_NUMBER_WHEN_DEVICE_LOST_IMS_REGISTRATION)
     public void clearPhoneNumberForSourceIms() {
         int subId = getSubId();
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -2683,6 +2704,11 @@
         return mImsStats;
     }
 
+    /** Returns the {@link AccessNetworkConstants.TransportType} used to register this IMS phone. */
+    public @AccessNetworkConstants.TransportType int getTransportType() {
+        return mTransportType;
+    }
+
     /** Sets the {@link ImsStats} mock for this IMS phone during unit testing. */
     @VisibleForTesting
     public void setImsStats(ImsStats imsStats) {
@@ -2733,7 +2759,8 @@
     private void updateImsRegistrationInfo(
             @RegistrationManager.ImsRegistrationState int regState,
             @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech,
-            @RegistrationManager.SuggestedAction int suggestedAction) {
+            @RegistrationManager.SuggestedAction int suggestedAction,
+            @AccessNetworkConstants.TransportType int transportType) {
 
         if (regState == mImsRegistrationState) {
             // In NOT_REGISTERED state, the current PLMN can be blocked with a suggested action.
@@ -2759,6 +2786,7 @@
                 mDefaultPhone.mCi.updateImsRegistrationInfo(regState, imsRadioTech, 0,
                         mImsRegistrationCapabilities, null);
                 mImsRegistrationTech = imsRadioTech;
+                mTransportType = transportType;
                 mNotifiedRegisteredState = true;
                 return;
             }
@@ -2766,6 +2794,7 @@
 
         mImsRegistrationState = regState;
         mImsRegistrationTech = imsRadioTech;
+        mTransportType = transportType;
         mImsRegistrationSuggestedAction = suggestedAction;
         if (regState == REGISTRATION_STATE_NOT_REGISTERED) {
             mImsDeregistrationTech = imsRadioTech;
@@ -2827,6 +2856,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/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index e95433c..dcb3b20 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -1259,8 +1259,6 @@
         }
     }
 
-    private @NonNull final FeatureFlags mFeatureFlags;
-
     //***** Events
 
 
@@ -1273,8 +1271,9 @@
     @VisibleForTesting
     public ImsPhoneCallTracker(ImsPhone phone, ConnectorFactory factory, Executor executor,
             FeatureFlags featureFlags) {
+        super(featureFlags);
+
         this.mPhone = phone;
-        mFeatureFlags = featureFlags;
         mConnectorFactory = factory;
         if (executor != null) {
             mExecutor = executor;
@@ -3581,14 +3580,13 @@
                 ImsPhoneConnection conn = findConnection(imsCall);
                 // Since onCallInitiating and onCallProgressing reset mPendingMO,
                 // we can't depend on mPendingMO.
-                if ((reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL
-                        || reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED
-                        || reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED)
-                        && conn != null) {
+                if (conn != null) {
                     logi("onCallStartFailed eccCategory=" + eccCategory);
-                    if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL
-                            || reasonInfo.getExtraCode()
-                                    == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY) {
+                    int reason = reasonInfo.getCode();
+                    int extraCode = reasonInfo.getExtraCode();
+                    if ((reason == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
+                            && extraCode == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY)
+                            || (reason == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL)) {
                         conn.setNonDetectableEmergencyCallInfo(eccCategory);
                     }
                     conn.setImsReasonInfo(reasonInfo);
@@ -4453,13 +4451,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) {
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/metrics/CellularSecurityTransparencyStats.java b/src/java/com/android/internal/telephony/metrics/CellularSecurityTransparencyStats.java
new file mode 100644
index 0000000..f77474f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/CellularSecurityTransparencyStats.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.metrics;
+
+import android.telephony.CellularIdentifierDisclosure;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.TelephonyStatsLog;
+import com.android.telephony.Rlog;
+
+/**
+ * Facilitates writing stats relating to cellular transparency features. Delegates the actual
+ * writing of stats out to {@link TelephonyStatsLog}.
+ */
+public class CellularSecurityTransparencyStats {
+
+    private static final String LOG_TAG = "CellularSecurityTransparencyStats";
+    private static final String LOG_DESCRIPTOR_SIM_MCC = "SIM MCC";
+    private static final String LOG_DESCRIPTOR_SIM_MNC = "SIM MNC";
+    private static final String LOG_DESCRIPTOR_DISCLOSURE_MCC = "disclosure MCC";
+    private static final String LOG_DESCRIPTOR_DISCLOSURE_MNC = "disclosure MNC";
+    private static final int DEFAULT_PLMN_PART = -1;
+
+    /**
+     * Log an identifier disclosure to be written out to {@link TelephonyStatsLog}
+     */
+    public void logIdentifierDisclosure(CellularIdentifierDisclosure disclosure, String simMcc,
+            String simMnc, boolean notificationsEnabled) {
+
+        int mcc = parsePlmnPartOrDefault(simMcc, LOG_DESCRIPTOR_SIM_MCC);
+        int mnc = parsePlmnPartOrDefault(simMnc, LOG_DESCRIPTOR_SIM_MNC);
+
+        int disclosureMcc = DEFAULT_PLMN_PART;
+        int disclosureMnc = DEFAULT_PLMN_PART;
+        String plmn = disclosure.getPlmn();
+        if (plmn != null) {
+            String[] plmnParts = plmn.split("-");
+            if (plmnParts.length == 2) {
+                disclosureMcc = parsePlmnPartOrDefault(plmnParts[0], LOG_DESCRIPTOR_DISCLOSURE_MCC);
+                disclosureMnc = parsePlmnPartOrDefault(plmnParts[1], LOG_DESCRIPTOR_DISCLOSURE_MNC);
+            }
+        }
+
+        writeIdentifierDisclosure(mcc, mnc, disclosureMcc, disclosureMnc,
+                disclosure.getCellularIdentifier(), disclosure.getNasProtocolMessage(),
+                disclosure.isEmergency(), notificationsEnabled);
+
+    }
+
+    private int parsePlmnPartOrDefault(String input, String logDescriptor) {
+        try {
+            return Integer.parseInt(input);
+        } catch (NumberFormatException e) {
+            Rlog.d(LOG_TAG, "Failed to parse " + logDescriptor + ": " + input);
+        }
+
+        return DEFAULT_PLMN_PART;
+    }
+
+    /**
+     * Write identifier disclosure data out to {@link TelephonyStatsLog}. This method is split
+     * out to enable testing, since {@link TelephonyStatsLog} is a final static class.
+     */
+    @VisibleForTesting
+    public void writeIdentifierDisclosure(int mcc, int mnc, int disclosureMcc, int disclosureMnc,
+            int identifier, int protocolMessage, boolean isEmergency,
+            boolean areNotificationsEnabled) {
+        TelephonyStatsLog.write(TelephonyStatsLog.CELLULAR_IDENTIFIER_DISCLOSED, mcc, mnc,
+                disclosureMcc, disclosureMnc, identifier, protocolMessage, isEmergency,
+                areNotificationsEnabled);
+    }
+}
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 387495e..cd5b7d6 100644
--- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
@@ -16,12 +16,27 @@
 
 package com.android.internal.telephony.metrics;
 
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
+
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.CellSignalStrength;
@@ -30,6 +45,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataCallResponse.LinkStatus;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
@@ -38,11 +54,14 @@
 import com.android.internal.telephony.data.DataNetworkController;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataStallRecoveryManager;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.telephony.Rlog;
 
 import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Generates metrics related to data stall recovery events per phone ID for the pushed atom.
@@ -57,18 +76,31 @@
 
     private static final String TAG = "DSRS-";
 
+    private static final int UNSET_DIAGNOSTIC_STATE = -1;
+
+    private static final long REFRESH_DURATION_IN_MILLIS = TimeUnit.MINUTES.toMillis(3);
+
     // Handler to upload metrics.
     private final @NonNull Handler mHandler;
 
     private final @NonNull String mTag;
     private final @NonNull Phone mPhone;
+    private final @NonNull TelephonyManager mTelephonyManager;
+    private final @NonNull FeatureFlags mFeatureFlags;
+
+    // Flag to control the DSRS diagnostics
+    private final boolean mIsDsrsDiagnosticsEnabled;
 
     // The interface name of the internet network.
     private @Nullable String mIfaceName = null;
 
     /* Metrics and stats data variables */
+    // Record metrics refresh time in milliseconds to decide whether to refresh data again
+    @ElapsedRealtimeLong
+    private long mMetricsReflashTime = 0L;
     private int mPhoneId = 0;
     private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+    private int mConvertedMccMnc = -1;
     private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
     private int mBand = 0;
     // The RAT used for data (including IWLAN).
@@ -88,17 +120,33 @@
     private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
     private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
 
+    // Connectivity diagnostics states
+    private int mNetworkProbesResult = UNSET_DIAGNOSTIC_STATE;
+    private int mNetworkProbesType = UNSET_DIAGNOSTIC_STATE;
+    private int mNetworkValidationResult = UNSET_DIAGNOSTIC_STATE;
+    private int mTcpMetricsCollectionPeriodMillis = UNSET_DIAGNOSTIC_STATE;
+    private int mTcpPacketFailRate = UNSET_DIAGNOSTIC_STATE;
+    private int mDnsConsecutiveTimeouts = UNSET_DIAGNOSTIC_STATE;
+
+    private ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager = null;
+    private ConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback = null;
+    private static final Executor INLINE_EXECUTOR = x -> x.run();
+
     /**
      * Constructs a new instance of {@link DataStallRecoveryStats}.
      */
-    public DataStallRecoveryStats(@NonNull final Phone phone,
+    public DataStallRecoveryStats(
+            @NonNull final Phone phone,
+            @NonNull FeatureFlags featureFlags,
             @NonNull final DataNetworkController dataNetworkController) {
         mTag = TAG + phone.getPhoneId();
         mPhone = phone;
+        mFeatureFlags = featureFlags;
 
         HandlerThread handlerThread = new HandlerThread(mTag + "-thread");
         handlerThread.start();
         mHandler = new Handler(handlerThread.getLooper());
+        mTelephonyManager = mPhone.getContext().getSystemService(TelephonyManager.class);
 
         dataNetworkController.registerDataNetworkControllerCallback(
                 new DataNetworkControllerCallback(mHandler::post) {
@@ -117,6 +165,45 @@
                     mInternetLinkStatus = status;
                 }
             });
+
+        mIsDsrsDiagnosticsEnabled = mFeatureFlags.dsrsDiagnosticsEnabled();
+        if (mIsDsrsDiagnosticsEnabled) {
+            try {
+                // Register ConnectivityDiagnosticsCallback to get diagnostics states
+                mConnectivityDiagnosticsManager =
+                    mPhone.getContext().getSystemService(ConnectivityDiagnosticsManager.class);
+                mConnectivityDiagnosticsCallback = new ConnectivityDiagnosticsCallback() {
+                    @Override
+                    public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) {
+                        PersistableBundle bundle = report.getAdditionalInfo();
+                        mNetworkProbesResult = bundle.getInt(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK);
+                        mNetworkProbesType = bundle.getInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK);
+                        mNetworkValidationResult = bundle.getInt(KEY_NETWORK_VALIDATION_RESULT);
+                    }
+
+                    @Override
+                    public void onDataStallSuspected(@NonNull DataStallReport report) {
+                        PersistableBundle bundle = report.getStallDetails();
+                        mTcpMetricsCollectionPeriodMillis =
+                            bundle.getInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS);
+                        mTcpPacketFailRate = bundle.getInt(KEY_TCP_PACKET_FAIL_RATE);
+                        mDnsConsecutiveTimeouts = bundle.getInt(KEY_DNS_CONSECUTIVE_TIMEOUTS);
+                    }
+                };
+                mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
+                    new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .build(),
+                        INLINE_EXECUTOR,
+                        mConnectivityDiagnosticsCallback
+                );
+            } catch (Exception e) {
+                mConnectivityDiagnosticsManager = null;
+                mConnectivityDiagnosticsCallback = null;
+            }
+        }
     }
 
     /**
@@ -194,10 +281,26 @@
      */
     private void refreshMetricsData() {
         logd("Refreshes the metrics data.");
+        // Update the metrics reflash time
+        mMetricsReflashTime = SystemClock.elapsedRealtime();
         // Update phone id/carrier id and signal strength
         mPhoneId = mPhone.getPhoneId() + 1;
         mCarrierId = mPhone.getCarrierId();
         mSignalStrength = mPhone.getSignalStrength().getLevel();
+        if (mIsDsrsDiagnosticsEnabled) {
+            // Get the MCCMNC and convert it to an int
+            String networkOperator = mTelephonyManager.getNetworkOperator();
+            if (!TextUtils.isEmpty(networkOperator)) {
+                try {
+                    mConvertedMccMnc = Integer.parseInt(networkOperator);
+                } catch (NumberFormatException e) {
+                    loge("Invalid MCCMNC format: " + networkOperator);
+                    mConvertedMccMnc = -1;
+                }
+            } else {
+                mConvertedMccMnc = -1;
+            }
+        }
 
         // Update the bandwidth.
         updateBandwidths();
@@ -308,7 +411,8 @@
      * @param isRecovered Whether the data stall has been recovered.
      * @param duration The duration from data stall occurred in milliseconds.
      * @param reason The reason for the recovery.
-     * @param isFirstValidation Whether this is the first validation after recovery.
+     * @param validationCount The total number of validation duration a data stall.
+     * @param actionValidationCount The number of validation for current action during a data stall
      * @param durationOfAction The duration of the current action in milliseconds.
      */
     public Bundle getDataStallRecoveryMetricsData(
@@ -316,28 +420,75 @@
             boolean isRecovered,
             int duration,
             @DataStallRecoveryManager.RecoveredReason int reason,
-            boolean isFirstValidation,
+            int validationCount,
+            int actionValidationCount,
             int durationOfAction) {
+
+        if (mIsDsrsDiagnosticsEnabled) {
+            // Refresh data if the data has not been updated within 3 minutes
+            final long refreshDuration = SystemClock.elapsedRealtime() - mMetricsReflashTime;
+            if (refreshDuration > REFRESH_DURATION_IN_MILLIS) {
+                // Refreshes the metrics data.
+                try {
+                    refreshMetricsData();
+                } catch (Exception e) {
+                    loge("The metrics data cannot be refreshed.", e);
+                }
+            }
+        }
+
         Bundle bundle = new Bundle();
-        bundle.putInt("Action", action);
-        bundle.putBoolean("IsRecovered", isRecovered);
-        bundle.putInt("Duration", duration);
-        bundle.putInt("Reason", reason);
-        bundle.putBoolean("IsFirstValidation", isFirstValidation);
-        bundle.putInt("DurationOfAction", durationOfAction);
-        bundle.putInt("PhoneId", mPhoneId);
-        bundle.putInt("CarrierId", mCarrierId);
-        bundle.putInt("SignalStrength", mSignalStrength);
-        bundle.putInt("Band", mBand);
-        bundle.putInt("Rat", mRat);
-        bundle.putBoolean("IsOpportunistic", mIsOpportunistic);
-        bundle.putBoolean("IsMultiSim", mIsMultiSim);
-        bundle.putInt("NetworkRegState", mNetworkRegState);
-        bundle.putInt("OtherSignalStrength", mOtherSignalStrength);
-        bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState);
-        bundle.putInt("InternetLinkStatus", mInternetLinkStatus);
-        bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps);
-        bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps);
+
+        if (mIsDsrsDiagnosticsEnabled) {
+            bundle.putInt("Action", action);
+            bundle.putInt("IsRecovered", isRecovered ? 1 : 0);
+            bundle.putInt("Duration", duration);
+            bundle.putInt("Reason", reason);
+            bundle.putInt("DurationOfAction", durationOfAction);
+            bundle.putInt("ValidationCount", validationCount);
+            bundle.putInt("ActionValidationCount", actionValidationCount);
+            bundle.putInt("PhoneId", mPhoneId);
+            bundle.putInt("CarrierId", mCarrierId);
+            bundle.putInt("MccMnc", mConvertedMccMnc);
+            bundle.putInt("SignalStrength", mSignalStrength);
+            bundle.putInt("Band", mBand);
+            bundle.putInt("Rat", mRat);
+            bundle.putInt("IsOpportunistic", mIsOpportunistic ? 1 : 0);
+            bundle.putInt("IsMultiSim", mIsMultiSim ? 1 : 0);
+            bundle.putInt("NetworkRegState", mNetworkRegState);
+            bundle.putInt("OtherSignalStrength", mOtherSignalStrength);
+            bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState);
+            bundle.putInt("InternetLinkStatus", mInternetLinkStatus);
+            bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps);
+            bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps);
+            bundle.putInt("NetworkProbesResult", mNetworkProbesResult);
+            bundle.putInt("NetworkProbesType", mNetworkProbesType);
+            bundle.putInt("NetworkValidationResult", mNetworkValidationResult);
+            bundle.putInt("TcpMetricsCollectionPeriodMillis", mTcpMetricsCollectionPeriodMillis);
+            bundle.putInt("TcpPacketFailRate", mTcpPacketFailRate);
+            bundle.putInt("DnsConsecutiveTimeouts", mDnsConsecutiveTimeouts);
+        } else {
+            bundle.putInt("Action", action);
+            bundle.putBoolean("IsRecovered", isRecovered);
+            bundle.putInt("Duration", duration);
+            bundle.putInt("Reason", reason);
+            bundle.putBoolean("IsFirstValidation", validationCount == 1);
+            bundle.putInt("DurationOfAction", durationOfAction);
+            bundle.putInt("PhoneId", mPhoneId);
+            bundle.putInt("CarrierId", mCarrierId);
+            bundle.putInt("SignalStrength", mSignalStrength);
+            bundle.putInt("Band", mBand);
+            bundle.putInt("Rat", mRat);
+            bundle.putBoolean("IsOpportunistic", mIsOpportunistic);
+            bundle.putBoolean("IsMultiSim", mIsMultiSim);
+            bundle.putInt("NetworkRegState", mNetworkRegState);
+            bundle.putInt("OtherSignalStrength", mOtherSignalStrength);
+            bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState);
+            bundle.putInt("InternetLinkStatus", mInternetLinkStatus);
+            bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps);
+            bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps);
+        }
+
         return bundle;
     }
 
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 04c9677..d9994aa 100644
--- a/src/java/com/android/internal/telephony/metrics/ImsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/ImsStats.java
@@ -239,6 +239,10 @@
                 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;
@@ -334,6 +338,13 @@
 
     /** 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();
@@ -341,6 +352,7 @@
         if (mLastRegistrationState == REGISTRATION_STATE_NOT_REGISTERED) {
             updateImsRegistrationStats();
         }
+
         mLastRegistrationStats.rat =
                 convertTransportTypeToNetworkType(attributes.getTransportType());
         mLastRegistrationStats.isIwlanCrossSim = attributes.getRegistrationTechnology()
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index 8bd2547..856045f 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -138,14 +138,6 @@
     private static final long MIN_COOLDOWN_MILLIS =
             DBG ? 10L * MILLIS_PER_SECOND : 23L * MILLIS_PER_HOUR;
 
-    /**
-     * Sets atom pull cool down to 4 minutes for userdebug build.
-     *
-     * <p>Applies to certain atoms: CellularServiceState.
-     */
-    private static final long CELL_SERVICE_MIN_COOLDOWN_MILLIS =
-            DBG ? 10L * MILLIS_PER_SECOND :
-                    IS_DEBUGGABLE ? 4L * MILLIS_PER_MINUTE : 23L * MILLIS_PER_HOUR;
 
     /**
      * Buckets with less than these many calls will be dropped.
@@ -166,24 +158,32 @@
     private static final long CELL_SERVICE_DURATION_BUCKET_MILLIS =
             DBG || IS_DEBUGGABLE ? 2L * MILLIS_PER_SECOND : 5L * MILLIS_PER_MINUTE;
 
+    /**
+     * Sets atom pull cool down to 4 minutes for userdebug build, 5 hours for user build.
+     *
+     * <p>Applies to certain atoms: CellularServiceState, DataCallSession,
+     * ImsRegistrationTermination.
+     */
+    private final long mPowerCorrelatedMinCooldownMillis;
     private final PersistAtomsStorage mStorage;
     private final DeviceStateHelper mDeviceStateHelper;
     private final StatsManager mStatsManager;
     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, @NonNull FeatureFlags featureFlags) {
         this(context, new PersistAtomsStorage(context),
-                new DeviceStateHelper(context), new VonrHelper(featureFlags));
+                new DeviceStateHelper(context), new VonrHelper(featureFlags), featureFlags);
     }
 
     /** Allows dependency injection. Used during unit tests. */
     @VisibleForTesting
     public MetricsCollector(
             Context context, PersistAtomsStorage storage, DeviceStateHelper deviceStateHelper,
-                    VonrHelper vonrHelper) {
+                    VonrHelper vonrHelper, @NonNull FeatureFlags featureFlags) {
         mStorage = storage;
         mDeviceStateHelper = deviceStateHelper;
         mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
@@ -232,6 +232,10 @@
         }
 
         mAirplaneModeStats = new AirplaneModeStats(context);
+        mDefaultNetworkMonitor = new DefaultNetworkMonitor(context, featureFlags);
+        mPowerCorrelatedMinCooldownMillis = DBG ? 10L * MILLIS_PER_SECOND :
+                IS_DEBUGGABLE ? 4L * MILLIS_PER_MINUTE : (long) context.getResources().getInteger(
+                com.android.internal.R.integer.config_metrics_pull_cooldown_millis);
     }
 
     /**
@@ -362,6 +366,10 @@
         mOngoingDataCallStats.remove(call);
     }
 
+    public DefaultNetworkMonitor getDefaultNetworkMonitor() {
+        return mDefaultNetworkMonitor;
+    }
+
     private void concludeDataCallSessionStats() {
         for (DataCallSessionStats stats : mOngoingDataCallStats) {
             stats.conclude();
@@ -510,7 +518,8 @@
     private int pullDataCallSession(List<StatsEvent> data) {
         // Include ongoing data call segments
         concludeDataCallSessionStats();
-        DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(MIN_COOLDOWN_MILLIS);
+        DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(
+                mPowerCorrelatedMinCooldownMillis);
         if (dataCallSessions != null) {
             Arrays.stream(dataCallSessions)
                     .forEach(dataCall -> data.add(buildStatsEvent(dataCall)));
@@ -538,8 +547,8 @@
     private int pullCellularServiceState(List<StatsEvent> data) {
         // Include the latest durations
         concludeServiceStateStats();
-        CellularServiceState[] persistAtoms =
-                mStorage.getCellularServiceStates(CELL_SERVICE_MIN_COOLDOWN_MILLIS);
+        CellularServiceState[] persistAtoms = mStorage.getCellularServiceStates(
+                mPowerCorrelatedMinCooldownMillis);
         if (persistAtoms != null) {
             // list is already shuffled when instances were inserted
             Arrays.stream(persistAtoms)
@@ -567,8 +576,8 @@
     }
 
     private int pullImsRegistrationTermination(List<StatsEvent> data) {
-        ImsRegistrationTermination[] persistAtoms =
-                mStorage.getImsRegistrationTerminations(MIN_COOLDOWN_MILLIS);
+        ImsRegistrationTermination[] persistAtoms = mStorage.getImsRegistrationTerminations(
+                mPowerCorrelatedMinCooldownMillis);
         if (persistAtoms != null) {
             // list is already shuffled when instances were inserted
             Arrays.stream(persistAtoms)
@@ -1118,7 +1127,8 @@
                 roundAndConvertMillisToSeconds(stats.utAvailableMillis),
                 roundAndConvertMillisToSeconds(stats.registeringMillis),
                 roundAndConvertMillisToSeconds(stats.unregisteredMillis),
-                stats.isIwlanCrossSim);
+                stats.isIwlanCrossSim,
+                stats.registeredTimes);
     }
 
     private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) {
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index 101df0d..f3fe8fa 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -409,6 +409,7 @@
             existingStats.utAvailableMillis += stats.utAvailableMillis;
             existingStats.registeringMillis += stats.registeringMillis;
             existingStats.unregisteredMillis += stats.unregisteredMillis;
+            existingStats.registeredTimes += stats.registeredTimes;
             existingStats.lastUsedMillis = getWallTimeMillis();
         } else {
             stats.lastUsedMillis = getWallTimeMillis();
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index 0c68d4b..877eaf1 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -18,6 +18,8 @@
 
 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;
@@ -29,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;
 
@@ -50,8 +53,12 @@
     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);
-    public static final long DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT =
-            TimeUnit.SECONDS.toMillis(60);
+    /** 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);
 
@@ -80,7 +87,8 @@
     private SatelliteDatagram mDemoModeDatagram;
     private boolean mIsDemoMode = false;
     private long mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
-    private long mDatagramWaitTimeForConnectedState = DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT;
+    private long mDatagramWaitTimeForConnectedState;
+    private long mModemImageSwitchingDuration;
     @GuardedBy("mLock")
     @SatelliteManager.SatelliteModemState
     private int mSatelltieModemState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
@@ -132,6 +140,9 @@
         // Create the DatagramReceiver singleton,
         // which is used to receive satellite datagrams.
         mDatagramReceiver = DatagramReceiver.make(mContext, looper, this);
+
+        mDatagramWaitTimeForConnectedState = getDatagramWaitForConnectedStateTimeoutMillis();
+        mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
     }
 
     /**
@@ -367,25 +378,51 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public long getDatagramWaitTimeForConnectedState() {
-        return mDatagramWaitTimeForConnectedState;
+        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;
-        mDatagramWaitTimeForConnectedState = 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;
     }
 
@@ -404,6 +441,16 @@
         }
     }
 
+    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
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
index e4d16e7..5cac1dd 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.satellite;
 
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import static com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT;
@@ -58,6 +59,8 @@
     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;
@@ -93,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.
@@ -126,6 +131,7 @@
         synchronized (mLock) {
             mSendingDatagramInProgress = false;
         }
+        mWaitTimeForDatagramSendingResponse = getWaitForDatagramSendingResponseTimeoutMillis();
     }
 
     private static final class DatagramDispatcherHandlerRequest {
@@ -196,20 +202,24 @@
                         (SendSatelliteDatagramArgument) request.argument;
                 onCompleted = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request);
 
-                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);
+                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;
 
@@ -223,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);
@@ -278,6 +300,11 @@
                 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;
@@ -330,7 +357,7 @@
             }
 
             if (mDatagramController.needsWaitingForSatelliteConnected()) {
-                logd("sendSatelliteDatagram: wait for satellite connected");
+                logd("sendDatagram: wait for satellite connected");
                 mDatagramController.updateSendStatus(subId,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
                         getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
@@ -345,7 +372,7 @@
                         getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
                 sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArgs, phone);
             } else {
-                logd("sendSatelliteDatagram: mSendingDatagramInProgress="
+                logd("sendDatagram: mSendingDatagramInProgress="
                         + mSendingDatagramInProgress + ", isPollingInIdleState="
                         + mDatagramController.isPollingInIdleState());
             }
@@ -596,6 +623,7 @@
 
         stopSatelliteAlignedTimer();
         stopDatagramWaitForConnectedStateTimer();
+        stopWaitForDatagramSendingResponseTimer();
         mIsDemoMode = false;
         mSendSatelliteDatagramRequest = null;
         mIsAligned = false;
@@ -620,6 +648,32 @@
         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) {
@@ -654,6 +708,61 @@
         }
     }
 
+    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
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
index 3ac1bbd..c267fd7 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
@@ -588,13 +588,14 @@
             @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.");
+            logd("pollPendingSatelliteDatagramsInternal: satellite modem is busy sending "
+                    + "datagrams.");
             callback.accept(SatelliteManager.SATELLITE_RESULT_MODEM_BUSY);
             return;
         }
 
         if (mDatagramController.needsWaitingForSatelliteConnected()) {
-            logd("pollPendingSatelliteDatagrams: wait for satellite connected");
+            logd("pollPendingSatelliteDatagramsInternal: wait for satellite connected");
             synchronized (mLock) {
                 mPendingPollSatelliteDatagramsRequest = new DatagramReceiverHandlerRequest(
                         callback, SatelliteServiceUtils.getPhone(), subId);
diff --git a/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
index 26100a8..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(subId);
+        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 9a6bd69..878ee96 100644
--- a/src/java/com/android/internal/telephony/satellite/PointingAppController.java
+++ b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
@@ -376,6 +376,7 @@
             loge("startPointingUI: launchIntent is null");
             return;
         }
+        logd("startPointingUI: needFullScreenPointingUI: " + needFullScreenPointingUI);
         launchIntent.putExtra("needFullScreen", needFullScreenPointingUI);
 
         try {
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java b/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java
new file mode 100644
index 0000000..8d7e723
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.satellite.nano.SatelliteConfigData;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * SatelliteConfig is utility class for satellite.
+ * It is obtained through the getConfig() at the SatelliteConfigParser.
+ */
+public class SatelliteConfig {
+
+    private static final String TAG = "SatelliteConfig";
+    private static final String SATELLITE_DIR_NAME = "satellite";
+    private static final String S2_CELL_FILE_NAME = "s2_cell_file";
+    private int mVersion;
+    private Map<Integer, Map<String, Set<Integer>>> mSupportedServicesPerCarrier;
+    private List<String> mSatelliteRegionCountryCodes;
+    private Boolean mIsSatelliteRegionAllowed;
+    private Path mSatS2FilePath;
+    private SatelliteConfigData.SatelliteConfigProto mConfigData;
+
+    public SatelliteConfig(SatelliteConfigData.SatelliteConfigProto configData) {
+        mConfigData = configData;
+        mVersion = mConfigData.version;
+        mSupportedServicesPerCarrier = getCarrierSupportedSatelliteServices();
+        mSatelliteRegionCountryCodes = List.of(
+                mConfigData.deviceSatelliteRegion.countryCodes);
+        mIsSatelliteRegionAllowed = mConfigData.deviceSatelliteRegion.isAllowed;
+        mSatS2FilePath = null;
+
+        Log.d(TAG, "mVersion:" + mVersion + " | "
+                + "mSupportedServicesPerCarrier:" + mSupportedServicesPerCarrier + " | "
+                + "mSatelliteRegionCountryCodes:" + mSatelliteRegionCountryCodes + " | "
+                + "mIsSatelliteRegionAllowed:" + mIsSatelliteRegionAllowed + " | "
+                + "s2CellFile size:" + mConfigData.deviceSatelliteRegion.s2CellFile.length);
+    }
+
+    /**
+     * @return a Map data with carrier_id, plmns and allowed_services.
+     */
+    private Map<Integer, Map<String, Set<Integer>>> getCarrierSupportedSatelliteServices() {
+        SatelliteConfigData.CarrierSupportedSatelliteServicesProto[] satelliteServices =
+                mConfigData.carrierSupportedSatelliteServices;
+        Map<Integer, Map<String, Set<Integer>>> carrierToServicesMap = new HashMap<>();
+        for (SatelliteConfigData.CarrierSupportedSatelliteServicesProto carrierProto :
+                satelliteServices) {
+            SatelliteConfigData.SatelliteProviderCapabilityProto[] satelliteCapabilities =
+                    carrierProto.supportedSatelliteProviderCapabilities;
+            Map<String, Set<Integer>> satelliteCapabilityMap = new HashMap<>();
+            for (SatelliteConfigData.SatelliteProviderCapabilityProto capabilityProto :
+                    satelliteCapabilities) {
+                String carrierPlmn = capabilityProto.carrierPlmn;
+                Set<Integer> allowedServices = new HashSet<>();
+                for (int service : capabilityProto.allowedServices) {
+                    allowedServices.add(service);
+                }
+                satelliteCapabilityMap.put(carrierPlmn, allowedServices);
+            }
+            carrierToServicesMap.put(carrierProto.carrierId, satelliteCapabilityMap);
+        }
+        return carrierToServicesMap;
+    }
+
+    /**
+     * Get satellite plmns for carrier
+     *
+     * @param carrierId the carrier identifier.
+     * @return Plmns corresponding to carrier identifier.
+     */
+    @NonNull
+    public List<String> getAllSatellitePlmnsForCarrier(int carrierId) {
+        if (mSupportedServicesPerCarrier != null) {
+            Map<String, Set<Integer>> satelliteCapabilitiesMap = mSupportedServicesPerCarrier.get(
+                    carrierId);
+            if (satelliteCapabilitiesMap != null) {
+                return new ArrayList<>(satelliteCapabilitiesMap.keySet());
+            }
+        }
+        Log.d(TAG, "getAllSatellitePlmnsForCarrier : mConfigData is null or no config data");
+        return new ArrayList<>();
+    }
+
+    /**
+     * Get supported satellite services of all providers for a carrier.
+     * The format of the return value - Key: PLMN, Value: Set of supported satellite services.
+     *
+     * @param carrierId the carrier identifier.
+     * @return all supported satellite services for a carrier
+     */
+    @NonNull
+    public Map<String, Set<Integer>> getSupportedSatelliteServices(int carrierId) {
+        if (mSupportedServicesPerCarrier != null) {
+            Map<String, Set<Integer>> satelliteCapaMap =
+                    mSupportedServicesPerCarrier.get(carrierId);
+            if (satelliteCapaMap != null) {
+                return satelliteCapaMap;
+            } else {
+                Log.d(TAG, "No supported services found for carrier=" + carrierId);
+            }
+        } else {
+            Log.d(TAG, "mSupportedServicesPerCarrier is null");
+        }
+        return new HashMap<>();
+    }
+
+    /**
+     * @return satellite region country codes
+     */
+    @NonNull
+    public List<String> getDeviceSatelliteCountryCodes() {
+        if (mSatelliteRegionCountryCodes != null) {
+            return mSatelliteRegionCountryCodes;
+        }
+        Log.d(TAG, "getDeviceSatelliteCountryCodes : mConfigData is null or no config data");
+        return new ArrayList<>();
+    }
+
+    /**
+     * @return satellite access allow value, if there is no config data then it returns null.
+     */
+    @Nullable
+    public Boolean isSatelliteDataForAllowedRegion() {
+        if (mIsSatelliteRegionAllowed == null) {
+            Log.d(TAG, "getIsSatelliteRegionAllowed : mConfigData is null or no config data");
+        }
+        return mIsSatelliteRegionAllowed;
+    }
+
+
+    /**
+     * @param context the Context
+     * @return satellite s2_cell_file path
+     */
+    @Nullable
+    public Path getSatelliteS2CellFile(@Nullable Context context) {
+        if (context == null) {
+            Log.d(TAG, "getSatelliteS2CellFile : context is null");
+            return null;
+        }
+
+        if (isFileExist(mSatS2FilePath)) {
+            Log.d(TAG, "File mSatS2FilePath is already exist");
+            return mSatS2FilePath;
+        }
+
+        if (mConfigData != null && mConfigData.deviceSatelliteRegion != null) {
+            mSatS2FilePath = copySatS2FileToPhoneDirectory(context,
+                    mConfigData.deviceSatelliteRegion.s2CellFile);
+            return mSatS2FilePath;
+        }
+        Log.d(TAG, "getSatelliteS2CellFile :"
+                + "mConfigData is null or mConfigData.deviceSatelliteRegion is null");
+        return null;
+    }
+
+    /**
+     * @param context       the Context
+     * @param byteArrayFile byte array type of protobuffer config data
+     * @return the satellite_cell_file path
+     */
+    @Nullable
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public Path copySatS2FileToPhoneDirectory(@Nullable Context context,
+            @Nullable byte[] byteArrayFile) {
+
+        if (context == null || byteArrayFile == null) {
+            Log.d(TAG, "copySatS2FileToPhoneDirectory : context or byteArrayFile are null");
+            return null;
+        }
+
+        File satS2FileDir = context.getDir(SATELLITE_DIR_NAME, Context.MODE_PRIVATE);
+        if (!satS2FileDir.exists()) {
+            satS2FileDir.mkdirs();
+        }
+
+        Path targetSatS2FilePath = satS2FileDir.toPath().resolve(S2_CELL_FILE_NAME);
+        try {
+            InputStream inputStream = new ByteArrayInputStream(byteArrayFile);
+            if (inputStream == null) {
+                Log.d(TAG, "copySatS2FileToPhoneDirectory: Resource=" + S2_CELL_FILE_NAME
+                        + " not found");
+            } else {
+                Files.copy(inputStream, targetSatS2FilePath, StandardCopyOption.REPLACE_EXISTING);
+            }
+        } catch (IOException ex) {
+            Log.e(TAG, "copySatS2FileToPhoneDirectory: ex=" + ex);
+        }
+        return targetSatS2FilePath;
+    }
+
+    /**
+     * @return {@code true} if the SatS2File is already existed and {@code false} otherwise.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isFileExist(Path filePath) {
+        return Files.exists(filePath);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConfigParser.java b/src/java/com/android/internal/telephony/satellite/SatelliteConfigParser.java
new file mode 100644
index 0000000..4ff1880
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteConfigParser.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.internal.telephony.configupdate.ConfigParser;
+import com.android.internal.telephony.satellite.nano.SatelliteConfigData;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * SatelliteConfigParser parses the config data and create SatelliteConfig.
+ * The config data is located at "/data/misc/telephonyconfig/telephony_config.pb".
+ * It is obtained through the getConfigParser() at the TelephonyConfigUpdateInstallReceiver.
+ */
+public class SatelliteConfigParser extends ConfigParser<SatelliteConfig> {
+    private static final String TAG = "SatelliteConfigParser";
+
+    /**
+     * Create an instance of SatelliteConfigParser with byte array data.
+     *
+     * @param data the config data formatted as byte array.
+     */
+    public SatelliteConfigParser(@Nullable byte[] data) {
+        super(data);
+    }
+
+    /**
+     * Create an instance of SatelliteConfigParser with InputStream data.
+     *
+     * @param input the config data formatted as InputStream.
+     */
+    public SatelliteConfigParser(@NonNull InputStream input)
+            throws IOException {
+        super(input);
+    }
+
+    /**
+     * Create an instance of SatelliteConfigParser with File data.
+     *
+     * @param file the config data formatted as File.
+     */
+    public SatelliteConfigParser(@NonNull File file) throws IOException {
+        super(file);
+    }
+
+    @Override
+    protected void parseData(@Nullable byte[] data) {
+        boolean parseError = false;
+        try {
+            if (data == null) {
+                Log.d(TAG, "config data is null");
+                return;
+            }
+            SatelliteConfigData.TelephonyConfigProto telephonyConfigData =
+                    SatelliteConfigData.TelephonyConfigProto.parseFrom(data);
+            if (telephonyConfigData == null || telephonyConfigData.satellite == null) {
+                Log.e(TAG, "telephonyConfigData or telephonyConfigData.satellite is null");
+                return;
+            }
+            mVersion = telephonyConfigData.satellite.version;
+            mConfig = new SatelliteConfig(telephonyConfigData.satellite);
+            Log.d(TAG, "SatelliteConfig is created");
+        } catch (Exception e) {
+            parseError = true;
+            Log.e(TAG, "Parse Error : " + e.getMessage());
+        } finally {
+            if (parseError) {
+                mVersion = VERSION_UNKNOWN;
+                mConfig = null;
+            }
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index b4ce9c7..92d9372 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -16,29 +16,43 @@
 
 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 static com.android.internal.telephony.configupdate.ConfigProviderAdaptor.DOMAIN_SATELLITE;
+
 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;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+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;
@@ -54,11 +68,14 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.Registrant;
+import android.os.RegistrantList;
 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;
@@ -68,14 +85,15 @@
 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.Pair;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.uwb.UwbManager;
@@ -88,6 +106,9 @@
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.configupdate.ConfigParser;
+import com.android.internal.telephony.configupdate.ConfigProviderAdaptor;
+import com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
@@ -102,9 +123,12 @@
 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.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 
 /**
@@ -123,6 +147,15 @@
     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
@@ -162,6 +195,8 @@
     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;
+    private static final int EVENT_SATELLITE_CONFIG_DATA_UPDATED = 40;
 
     @NonNull private static SatelliteController sInstance;
     @NonNull private final Context mContext;
@@ -247,9 +282,11 @@
     @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;
@@ -271,6 +308,7 @@
     @NonNull private final CarrierConfigManager mCarrierConfigManager;
     @NonNull private final CarrierConfigManager.CarrierConfigChangeListener
             mCarrierConfigChangeListener;
+    @NonNull private final ConfigProviderAdaptor.Callback mConfigDataUpdatedCallback;
     @NonNull private final Object mCarrierConfigArrayLock = new Object();
     @GuardedBy("mCarrierConfigArrayLock")
     @NonNull private final SparseArray<PersistableBundle> mCarrierConfigArray = new SparseArray<>();
@@ -309,6 +347,36 @@
     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";
+
+    private final RegistrantList mSatelliteConfigUpdateChangedRegistrants = new RegistrantList();
 
     /**
      * @return The singleton instance of SatelliteController.
@@ -369,13 +437,6 @@
         // 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();
@@ -406,8 +467,62 @@
                         handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
         mCarrierConfigManager.registerCarrierConfigChangeListener(
                         new HandlerExecutor(new Handler(looper)), mCarrierConfigChangeListener);
+
+        mConfigDataUpdatedCallback = new ConfigProviderAdaptor.Callback() {
+            @Override
+            public void onChanged(@Nullable ConfigParser config) {
+                SatelliteControllerHandlerRequest request =
+                        new SatelliteControllerHandlerRequest(true,
+                                SatelliteServiceUtils.getPhone());
+                sendRequestAsync(EVENT_SATELLITE_CONFIG_DATA_UPDATED, request, null);
+            }
+        };
+        TelephonyConfigUpdateInstallReceiver.getInstance()
+                .registerCallback(Executors.newSingleThreadExecutor(), mConfigDataUpdatedCallback);
+
         mDSM.registerForSignalStrengthReportDecision(this, CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING,
                 null);
+        loadSatelliteSharedPreferences();
+        mWaitTimeForSatelliteEnablingResponse = getWaitForSatelliteEnablingResponseTimeoutMillis();
+    }
+
+    /**
+     * Register a callback to get a updated satellite config data.
+     * @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 registerForConfigUpdateChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mSatelliteConfigUpdateChangedRegistrants.add(r);
+    }
+
+    /**
+     * Unregister a callback to get a updated satellite config data.
+     * @param h Handler to notify
+     */
+    public void unregisterForConfigUpdateChanged(Handler h) {
+        mSatelliteConfigUpdateChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Get satelliteConfig from SatelliteConfigParser
+     */
+    public SatelliteConfig getSatelliteConfig() {
+        if (getSatelliteConfigParser() == null) {
+            Log.d(TAG, "getSatelliteConfigParser() is not ready");
+            return null;
+        }
+        return (SatelliteConfig) getSatelliteConfigParser().getConfig();
+    }
+
+    /**
+     * Get SatelliteConfigParser from TelephonyConfigUpdateInstallReceiver
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public SatelliteConfigParser getSatelliteConfigParser() {
+        return (SatelliteConfigParser) TelephonyConfigUpdateInstallReceiver
+                .getInstance().getConfigParser(DOMAIN_SATELLITE);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -615,12 +730,15 @@
         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));
         }
     }
 
@@ -773,19 +891,23 @@
                 int error =  SatelliteServiceUtils.getSatelliteError(ar, "setSatelliteEnabled");
                 logd("EVENT_SET_SATELLITE_ENABLED_DONE = " + error);
 
+                /*
+                 * The timer to wait for EVENT_SET_SATELLITE_ENABLED_DONE might have expired and
+                 * thus the request resources might have been cleaned up.
+                 */
+                if (!shouldProcessEventSetSatelliteEnabledDone(argument)) {
+                    logw("The request ID=" + argument.requestId + ", enableSatellite="
+                            + 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 {
                         /**
@@ -855,6 +977,11 @@
                 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);
@@ -903,7 +1030,7 @@
                         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);
                     }
@@ -1014,19 +1141,21 @@
             case EVENT_RADIO_STATE_CHANGED: {
                 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);
                             }
                         }
                     }
@@ -1042,25 +1171,7 @@
             }
 
             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 == SATELLITE_RESULT_SUCCESS) {
-                    if (ar.result == null) {
-                        loge("isSatelliteProvisioned: result is null");
-                        error = SatelliteManager.SATELLITE_RESULT_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;
             }
 
@@ -1081,7 +1192,7 @@
                         logd("pollPendingSatelliteDatagram result: " + result);
                     }
                 };
-                pollPendingSatelliteDatagrams(
+                pollPendingDatagrams(
                         SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, internalCallback);
                 break;
 
@@ -1241,6 +1352,12 @@
                 break;
             }
 
+            case EVENT_SATELLITE_CONFIG_DATA_UPDATED: {
+                handleEventConfigDataUpdated();
+                mSatelliteConfigUpdateChangedRegistrants.notifyRegistrants();
+                break;
+            }
+
             default:
                 Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " +
                         msg.what);
@@ -1248,6 +1365,19 @@
         }
     }
 
+    private void handleEventConfigDataUpdated() {
+        updateSupportedSatelliteServicesForActiveSubscriptions();
+        int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(true);
+        if (activeSubIds != null) {
+            for (int subId : activeSubIds) {
+                processNewCarrierConfigData(subId);
+            }
+        } else {
+            loge("updateSupportedSatelliteServicesForActiveSubscriptions: "
+                    + "activeSubIds is null");
+        }
+    }
+
     private void notifyRequester(SatelliteControllerHandlerRequest request) {
         synchronized (request) {
             request.notifyAll();
@@ -1660,11 +1790,11 @@
             return;
         }
 
-        synchronized (mIsSatelliteProvisionedLock) {
-            if (mIsSatelliteProvisioned != null) {
+        synchronized (mSatelliteViaOemProvisionLock) {
+            if (mIsSatelliteViaOemProvisioned != null) {
                 Bundle bundle = new Bundle();
                 bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED,
-                        mIsSatelliteProvisioned);
+                        mIsSatelliteViaOemProvisioned);
                 result.send(SATELLITE_RESULT_SUCCESS, bundle);
                 return;
             }
@@ -1682,7 +1812,7 @@
      * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
     @SatelliteManager.SatelliteResult public int registerForSatelliteModemStateChanged(int subId,
-            @NonNull ISatelliteStateCallback callback) {
+            @NonNull ISatelliteModemStateCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             logd("registerForSatelliteModemStateChanged: oemEnabledSatelliteFlag is disabled");
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
@@ -1703,18 +1833,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("unregisterForSatelliteModemStateChanged: oemEnabledSatelliteFlag is disabled");
+            logd("unregisterForModemStateChanged: oemEnabledSatelliteFlag is disabled");
             return;
         }
         if (mSatelliteSessionController != null) {
             mSatelliteSessionController.unregisterForSatelliteModemStateChanged(callback);
         } else {
-            loge("registerForSatelliteModemStateChanged: mSatelliteSessionController"
+            loge("unregisterForModemStateChanged: mSatelliteSessionController"
                     + " is not initialized yet");
         }
     }
@@ -1727,16 +1857,16 @@
      *
      * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
-    @SatelliteManager.SatelliteResult public int registerForSatelliteDatagram(int subId,
+    @SatelliteManager.SatelliteResult public int registerForIncomingDatagram(int subId,
             @NonNull ISatelliteDatagramCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("registerForSatelliteDatagram: oemEnabledSatelliteFlag is disabled");
+            logd("registerForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
         if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
-        logd("registerForSatelliteDatagram: callback=" + callback);
+        logd("registerForIncomingDatagram: callback=" + callback);
         return mDatagramController.registerForSatelliteDatagram(subId, callback);
     }
 
@@ -1746,18 +1876,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("unregisterForSatelliteDatagram: oemEnabledSatelliteFlag is disabled");
+            logd("unregisterForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
             return;
         }
         if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
             return;
         }
-        logd("unregisterForSatelliteDatagram: callback=" + callback);
+        logd("unregisterForIncomingDatagram: callback=" + callback);
         mDatagramController.unregisterForSatelliteDatagram(subId, callback);
     }
 
@@ -1772,7 +1902,7 @@
      * @param subId The subId of the subscription used for receiving datagrams.
      * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
-    public void pollPendingSatelliteDatagrams(int subId, @NonNull IIntegerConsumer callback) {
+    public void pollPendingDatagrams(int subId, @NonNull IIntegerConsumer callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error != SATELLITE_RESULT_SUCCESS) {
@@ -1800,9 +1930,12 @@
      *                                 full screen mode.
      * @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) {
+        logd("sendSatelliteDatagram: subId: " + subId + " datagramType: " + datagramType
+                + " needFullScreenPointingUI: " + needFullScreenPointingUI);
+
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error != SATELLITE_RESULT_SUCCESS) {
@@ -1883,14 +2016,14 @@
      * @param reason Reason for disallowing satellite communication for carrier.
      * @param callback The callback to get the result of the request.
      */
-    public void addSatelliteAttachRestrictionForCarrier(int subId,
+    public void addAttachRestrictionForCarrier(int subId,
             @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
             @NonNull IIntegerConsumer callback) {
-        if (DBG) logd("addSatelliteAttachRestrictionForCarrier(" + subId + ", " + reason + ")");
+        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("addSatelliteAttachRestrictionForCarrier: carrierEnabledSatelliteFlag is "
+            logd("addAttachRestrictionForCarrier: carrierEnabledSatelliteFlag is "
                     + "disabled");
             return;
         }
@@ -1922,14 +2055,14 @@
      * @param reason Reason for disallowing satellite communication.
      * @param callback The callback to get the result of the request.
      */
-    public void removeSatelliteAttachRestrictionForCarrier(int subId,
+    public void removeAttachRestrictionForCarrier(int subId,
             @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
             @NonNull IIntegerConsumer callback) {
-        if (DBG) logd("removeSatelliteAttachRestrictionForCarrier(" + subId + ", " + reason + ")");
+        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("removeSatelliteAttachRestrictionForCarrier: carrierEnabledSatelliteFlag is "
+            logd("removeAttachRestrictionForCarrier: carrierEnabledSatelliteFlag is "
                     + "disabled");
             return;
         }
@@ -1952,15 +2085,15 @@
 
     /**
      * Get reasons for disallowing satellite communication, as requested by
-     * {@link #addSatelliteAttachRestrictionForCarrier(int, int, IIntegerConsumer)}.
+     * {@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> getSatelliteAttachRestrictionReasonsForCarrier(int subId) {
+    @NonNull public Set<Integer> getAttachRestrictionReasonsForCarrier(int subId) {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
-            logd("getSatelliteAttachRestrictionReasonsForCarrier: carrierEnabledSatelliteFlag is "
+            logd("getAttachRestrictionReasonsForCarrier: carrierEnabledSatelliteFlag is "
                     + "disabled");
             return new HashSet<>();
         }
@@ -2027,7 +2160,7 @@
         if (error == SATELLITE_RESULT_SUCCESS) {
             mNtnSignalStrengthChangedListeners.put(callback.asBinder(), callback);
         } else {
-            throw new ServiceSpecificException(error);
+            throw new RemoteException(new IllegalStateException("registration fails: " + error));
         }
     }
 
@@ -2058,9 +2191,9 @@
      *
      * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
-    @SatelliteManager.SatelliteResult public int registerForSatelliteCapabilitiesChanged(
+    @SatelliteManager.SatelliteResult public int registerForCapabilitiesChanged(
             int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
-        if (DBG) logd("registerForSatelliteCapabilitiesChanged()");
+        if (DBG) logd("registerForCapabilitiesChanged()");
 
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error != SATELLITE_RESULT_SUCCESS) return error;
@@ -2076,11 +2209,11 @@
      * @param subId The id of the subscription to unregister for listening satellite capabilities
      * changed event.
      * @param callback The callback that was passed to
-     * {@link #registerForSatelliteCapabilitiesChanged(int, ISatelliteCapabilitiesCallback)}
+     * {@link #registerForCapabilitiesChanged(int, ISatelliteCapabilitiesCallback)}
      */
-    public void unregisterForSatelliteCapabilitiesChanged(
+    public void unregisterForCapabilitiesChanged(
             int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
-        if (DBG) logd("unregisterForSatelliteCapabilitiesChanged()");
+        if (DBG) logd("unregisterForCapabilitiesChanged()");
 
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error == SATELLITE_RESULT_SUCCESS) {
@@ -2110,8 +2243,8 @@
         synchronized (mIsSatelliteSupportedLock) {
             mIsSatelliteSupported = null;
         }
-        synchronized (mIsSatelliteProvisionedLock) {
-            mIsSatelliteProvisioned = null;
+        synchronized (mSatelliteViaOemProvisionLock) {
+            mIsSatelliteViaOemProvisioned = null;
         }
         synchronized (mIsSatelliteEnabledLock) {
             mIsSatelliteEnabled = null;
@@ -2144,18 +2277,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) {
+    public boolean setDatagramControllerTimeoutDuration(
+            boolean reset, int timeoutType, long timeoutMillis) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setSatelliteDeviceAlignedTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+            logd("setDatagramControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
             return false;
         }
-        return mDatagramController.setSatelliteDeviceAlignedTimeoutDuration(timeoutMillis);
+        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;
     }
 
     /**
@@ -2224,6 +2395,29 @@
         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;
@@ -2266,8 +2460,8 @@
                         @Override
                         protected void onReceiveResult(
                                 int resultCode, Bundle resultData) {
-                            logd("requestIsSatelliteSupported: resultCode="
-                                    + resultCode);
+                            logd("onSatelliteServiceConnected.requestIsSatelliteSupported:"
+                                    + " resultCode=" + resultCode);
                         }
                     };
                     requestIsSatelliteSupported(
@@ -2320,17 +2514,19 @@
      * @return The list of satellite PLMNs used for connecting to satellite networks.
      */
     @NonNull
-    public List<String> getSatellitePlmnList(int subId) {
+    public List<String> getSatellitePlmnsForCarrier(int subId) {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
-            logd("getSatellitePlmnList: carrierEnabledSatelliteFlag is disabled");
+            logd("getSatellitePlmnsForCarrier: carrierEnabledSatelliteFlag is disabled");
             return new ArrayList<>();
         }
+
+        if (!isSatelliteSupportedViaCarrier(subId)) {
+            logd("Satellite for carrier is not supported.");
+            return new ArrayList<>();
+        }
+
         synchronized (mSupportedSatelliteServicesLock) {
-            if (mSatelliteServicesSupportedByCarriers.containsKey(subId)) {
-                return new ArrayList<>(mSatelliteServicesSupportedByCarriers.get(subId).keySet());
-            } else {
-                return new ArrayList<>();
-            }
+            return mMergedPlmnListPerCarrier.get(subId, new ArrayList<>()).stream().toList();
         }
     }
 
@@ -2407,21 +2603,21 @@
     }
 
     /**
-     * @return {@code true} if any subscription on the device is connected to satellite,
-     * {@code false} otherwise.
+     * @return {@code Pair<true, subscription ID>} if any subscription on the device is connected to
+     * satellite, {@code Pair<false, null>} otherwise.
      */
-    private boolean isUsingNonTerrestrialNetworkViaCarrier() {
+    private Pair<Boolean, Integer> isUsingNonTerrestrialNetworkViaCarrier() {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
             logd("isUsingNonTerrestrialNetwork: carrierEnabledSatelliteFlag is disabled");
-            return false;
+            return new Pair<>(false, null);
         }
         for (Phone phone : PhoneFactory.getPhones()) {
             ServiceState serviceState = phone.getServiceState();
             if (serviceState != null && serviceState.isUsingNonTerrestrialNetwork()) {
-                return true;
+                return new Pair<>(true, phone.getSubId());
             }
         }
-        return false;
+        return new Pair<>(false, null);
     }
 
     /**
@@ -2435,7 +2631,7 @@
                     + " is disabled");
             return false;
         }
-        if (isUsingNonTerrestrialNetworkViaCarrier()) {
+        if (isUsingNonTerrestrialNetworkViaCarrier().first) {
             return true;
         }
         for (Phone phone : PhoneFactory.getPhones()) {
@@ -2472,6 +2668,62 @@
     }
 
     /**
+     * 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.
      */
@@ -2490,7 +2742,8 @@
                 new ResultReceiver(this) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        logd("requestIsSatelliteSupported: resultCode=" + resultCode);
+                        logd("isSatelliteSupportedViaOemInternal.requestIsSatelliteSupported:"
+                                + " resultCode=" + resultCode);
                     }
                 });
         return null;
@@ -2514,7 +2767,17 @@
                     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(
@@ -2527,13 +2790,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) {
@@ -2606,10 +2879,15 @@
      * @return true if satellite is provisioned on the given subscription else return false.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @Nullable
     protected Boolean isSatelliteViaOemProvisioned() {
-        synchronized (mIsSatelliteProvisionedLock) {
-            if (mIsSatelliteProvisioned != null) {
-                return mIsSatelliteProvisioned;
+        synchronized (mSatelliteViaOemProvisionLock) {
+            if (mOverriddenIsSatelliteViaOemProvisioned != null) {
+                return mOverriddenIsSatelliteViaOemProvisioned;
+            }
+
+            if (mIsSatelliteViaOemProvisioned != null) {
+                return mIsSatelliteViaOemProvisioned;
             }
         }
 
@@ -2617,7 +2895,7 @@
                 new ResultReceiver(this) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        logd("requestIsSatelliteProvisioned: resultCode=" + resultCode);
+                        logd("isSatelliteViaOemProvisioned: resultCode=" + resultCode);
                     }
                 });
         return null;
@@ -2636,6 +2914,7 @@
         Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request);
         mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite,
                 argument.enableDemoMode, onCompleted);
+        startWaitForSatelliteEnablingResponseTimer(argument);
     }
 
     private void handleRequestSatelliteAttachRestrictionForCarrierCmd(
@@ -2664,13 +2943,14 @@
             registerForPendingDatagramCount();
             registerForSatelliteModemStateChanged();
             registerForNtnSignalStrengthChanged();
-            registerForSatelliteCapabilitiesChanged();
+            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() {
@@ -2685,7 +2965,8 @@
                     new ResultReceiver(this) {
                         @Override
                         protected void onReceiveResult(int resultCode, Bundle resultData) {
-                            logd("requestSatelliteCapabilities: resultCode=" + resultCode);
+                            logd("requestSatelliteCapabilities: resultCode=" + resultCode
+                                    + ", resultData=" + resultData);
                         }
                     });
         }
@@ -2748,9 +3029,9 @@
         }
     }
 
-    private void registerForSatelliteCapabilitiesChanged() {
+    private void registerForCapabilitiesChanged() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("registerForSatelliteCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
+            logd("registerForCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
             return;
         }
 
@@ -2766,8 +3047,9 @@
     private void handleEventSatelliteProvisionStateChanged(boolean provisioned) {
         logd("handleSatelliteProvisionStateChangedEvent: provisioned=" + provisioned);
 
-        synchronized (mIsSatelliteProvisionedLock) {
-            mIsSatelliteProvisioned = provisioned;
+        synchronized (mSatelliteViaOemProvisionLock) {
+            persistOemEnabledSatelliteProvisionStatus(provisioned);
+            mIsSatelliteViaOemProvisioned = provisioned;
         }
 
         List<ISatelliteProvisionStateCallback> deadCallersList = new ArrayList<>();
@@ -2950,13 +3232,8 @@
             return;
         }
         synchronized (mSupportedSatelliteServicesLock) {
-            List<String> carrierPlmnList;
-            if (mSatelliteServicesSupportedByCarriers.containsKey(subId)) {
-                carrierPlmnList =
-                        mSatelliteServicesSupportedByCarriers.get(subId).keySet().stream().toList();
-            } else {
-                carrierPlmnList = new ArrayList<>();
-            }
+            List<String> carrierPlmnList = mMergedPlmnListPerCarrier.get(subId,
+                    new ArrayList<>()).stream().toList();
             int slotId = SubscriptionManager.getSlotIndex(subId);
             mSatelliteModemInterface.setSatellitePlmn(slotId, carrierPlmnList,
                     SatelliteServiceUtils.mergeStrLists(
@@ -2979,6 +3256,7 @@
 
         synchronized (mSupportedSatelliteServicesLock) {
             mSatelliteServicesSupportedByCarriers.clear();
+            mMergedPlmnListPerCarrier.clear();
             int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(true);
             if (activeSubIds != null) {
                 for (int subId : activeSubIds) {
@@ -2991,10 +3269,71 @@
         }
     }
 
-    private void updateSupportedSatelliteServices(int subId) {
+    /**
+     * If the entitlementPlmnList exist then used it.
+     * Otherwise, If the carrierPlmnList exist then used it.
+     */
+    private void updatePlmnListPerCarrier(int subId) {
         synchronized (mSupportedSatelliteServicesLock) {
+            List<String> carrierPlmnList, entitlementPlmnList;
+            entitlementPlmnList = mEntitlementPlmnListPerCarrier.get(subId,
+                    new ArrayList<>()).stream().toList();
+            if (!entitlementPlmnList.isEmpty()) {
+                mMergedPlmnListPerCarrier.put(subId, entitlementPlmnList);
+                logd("update it using entitlementPlmnList=" + entitlementPlmnList);
+                return;
+            }
+
+            SatelliteConfig satelliteConfig = getSatelliteConfig();
+            if (satelliteConfig != null) {
+                TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+                int carrierId = tm.createForSubscriptionId(subId).getSimCarrierId();
+                List<String> plmnList = satelliteConfig.getAllSatellitePlmnsForCarrier(carrierId);
+                if (!plmnList.isEmpty()) {
+                    logd("mMergedPlmnListPerCarrier is updated by ConfigUpdater : " + plmnList);
+                    mMergedPlmnListPerCarrier.put(subId, plmnList);
+                    return;
+                }
+            }
+
+            if (mSatelliteServicesSupportedByCarriers.containsKey(subId)) {
+                carrierPlmnList =
+                        mSatelliteServicesSupportedByCarriers.get(subId).keySet().stream().toList();
+                logd("mMergedPlmnListPerCarrier is updated by carrier config");
+            } else {
+                carrierPlmnList = new ArrayList<>();
+            }
+            mMergedPlmnListPerCarrier.put(subId, carrierPlmnList);
+            logd("update it using carrierPlmnList=" + carrierPlmnList);
+        }
+    }
+
+    private void updateSupportedSatelliteServices(int subId) {
+        logd("updateSupportedSatelliteServices with subId " + subId);
+        synchronized (mSupportedSatelliteServicesLock) {
+            SatelliteConfig satelliteConfig = getSatelliteConfig();
+
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            int carrierId = tm.createForSubscriptionId(subId).getSimCarrierId();
+
+            if (satelliteConfig != null) {
+                Map<String, Set<Integer>> supportedServicesPerPlmn =
+                        satelliteConfig.getSupportedSatelliteServices(carrierId);
+                if (!supportedServicesPerPlmn.isEmpty()) {
+                    mSatelliteServicesSupportedByCarriers.put(subId, supportedServicesPerPlmn);
+                    logd("updateSupportedSatelliteServices using ConfigUpdater, "
+                            + "supportedServicesPerPlmn = " + supportedServicesPerPlmn);
+                    updatePlmnListPerCarrier(subId);
+                    return;
+                } else {
+                    logd("supportedServicesPerPlmn is empty");
+                }
+            }
+
             mSatelliteServicesSupportedByCarriers.put(
                     subId, readSupportedSatelliteServicesFromCarrierConfig(subId));
+            updatePlmnListPerCarrier(subId);
+            logd("updateSupportedSatelliteServices using carrier config");
         }
     }
 
@@ -3028,7 +3367,8 @@
         PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId,
                 KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
                 KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
-                KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT);
+                KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT,
+                KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL);
         if (config == null || config.isEmpty()) {
             config = CarrierConfigManager.getDefaultConfig();
         }
@@ -3045,15 +3385,20 @@
         }
 
         updateCarrierConfig(subId);
+        updateEntitlementPlmnListPerCarrier(subId);
         updateSupportedSatelliteServicesForActiveSubscriptions();
-        configureSatellitePlmnForCarrier(subId);
+        processNewCarrierConfigData(subId);
+    }
 
+    private void processNewCarrierConfigData(int subId) {
+        configureSatellitePlmnForCarrier(subId);
         synchronized (mIsSatelliteEnabledLock) {
             mSatelliteAttachRestrictionForCarrierArray.clear();
             mIsSatelliteAttachEnabledForCarrierArrayPerSub.clear();
         }
 
         setSatelliteAttachEnabledForCarrierOnSimLoaded(subId);
+        updateRestrictReasonForEntitlementPerCarrier(subId);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -3063,6 +3408,31 @@
         }
     }
 
+    /** 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.
@@ -3167,6 +3537,54 @@
         }
     }
 
+    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.
@@ -3229,7 +3647,7 @@
      * <ul>
      * <li>Users want to enable it.</li>
      * <li>There is no satellite communication restriction, which is added by
-     * {@link #addSatelliteAttachRestrictionForCarrier(int, int, IIntegerConsumer)}</li>
+     * {@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>
@@ -3282,11 +3700,13 @@
             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) {
@@ -3296,6 +3716,7 @@
         if (isProvisionRequired) {
             Boolean satelliteProvisioned = isSatelliteViaOemProvisioned();
             if (satelliteProvisioned == null) {
+                logd("evaluateOemSatelliteRequestAllowed: satelliteProvisioned is null");
                 return SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
             }
             if (!satelliteProvisioned) {
@@ -3341,6 +3762,7 @@
 
     private void handleEventServiceStateChanged() {
         handleServiceStateForSatelliteConnectionViaCarrier();
+        determineSystemNotification();
     }
 
     private void handleServiceStateForSatelliteConnectionViaCarrier() {
@@ -3380,6 +3802,181 @@
         }
     }
 
+    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
@@ -3405,10 +4002,93 @@
         return true;
     }
 
+    private void determineSystemNotification() {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("determineSystemNotification: carrierEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        Pair<Boolean, Integer> isNtn = isUsingNonTerrestrialNetworkViaCarrier();
+        if (isNtn.first) {
+            if (mSharedPreferences == null) {
+                try {
+                    mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
+                            Context.MODE_PRIVATE);
+                } catch (Exception e) {
+                    loge("Cannot get default shared preferences: " + e);
+                }
+            }
+            if (mSharedPreferences == null) {
+                loge("determineSystemNotification: Cannot get default shared preferences");
+                return;
+            }
+            if (!mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false)) {
+                showSatelliteSystemNotification(isNtn.second);
+                mSharedPreferences.edit().putBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY,
+                        true).apply();
+            }
+        }
+    }
+
+    private void showSatelliteSystemNotification(int subId) {
+        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);
+        intentSatelliteSetting.putExtra("sub_id", subId);
+        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 03481c6..2f86eea 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -915,18 +915,18 @@
                     @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);
+                loge("pollPendingDatagrams: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("pollPendingSatelliteDatagrams: Satellite service is unavailable.");
+            loge("pollPendingDatagrams: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -951,18 +951,18 @@
                             @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);
+                loge("sendDatagram: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("sendSatelliteDatagram: Satellite service is unavailable.");
+            loge("sendDatagram: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -1021,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));
@@ -1029,7 +1029,7 @@
                         }, new IBooleanConsumer.Stub() {
                             @Override
                             public void accept(boolean result) {
-                                logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: "
+                                logd("requestIsCommunicationAllowedForCurrentLocation: "
                                         + result);
                                 Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
                                         message, result,
@@ -1037,13 +1037,13 @@
                             }
                         });
             } catch (RemoteException e) {
-                loge("requestIsSatelliteCommunicationAllowedForCurrentLocation: RemoteException "
+                loge("requestIsCommunicationAllowedForCurrentLocation: RemoteException "
                         + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestIsSatelliteCommunicationAllowedForCurrentLocation: "
+            loge("requestIsCommunicationAllowedForCurrentLocation: "
                     + "Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
@@ -1236,10 +1236,13 @@
                             }
                         }, new INtnSignalStrengthConsumer.Stub() {
                             @Override
-                            public void accept(NtnSignalStrength result) {
-                                logd("requestNtnSignalStrength: " + result);
+                            public void accept(
+                                    android.telephony.satellite.stub.NtnSignalStrength result) {
+                                NtnSignalStrength ntnSignalStrength =
+                                        SatelliteServiceUtils.fromNtnSignalStrength(result);
+                                logd("requestNtnSignalStrength: " + ntnSignalStrength);
                                 Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                        message, result,
+                                        message, ntnSignalStrength,
                                         SatelliteManager.SATELLITE_RESULT_SUCCESS));
                             }
                         });
@@ -1314,10 +1317,46 @@
         }
     }
 
+    /**
+     * 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);
+        }
+    }
+
     public boolean isSatelliteServiceSupported() {
         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.
      *
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 f40880b..c491476 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
@@ -23,8 +23,6 @@
 import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT;
 import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
 import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
-import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import static com.android.internal.telephony.satellite.SatelliteController.INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
 
@@ -39,7 +37,7 @@
 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.Connection;
@@ -51,6 +49,7 @@
 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;
@@ -58,6 +57,7 @@
 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;
@@ -83,6 +83,7 @@
     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
@@ -94,12 +95,17 @@
     /** Key: Phone ID; Value: IMS RegistrationCallback */
     private SparseArray<RegistrationManager.RegistrationCallback>
             mImsRegistrationCallbacks = new SparseArray<>();
-    private AtomicBoolean mIsSatelliteAllowedInCurrentLocation = new AtomicBoolean();
-    private final ResultReceiver mReceiverForRequestIsSatelliteAllowedForCurrentLocation;
+    @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 final Object mLock = new Object();
 
     /**
      * Create an instance of SatelliteSOSMessageRecommender.
@@ -139,31 +145,6 @@
                 sendMessage(obtainMessage(EVENT_SATELLITE_PROVISIONED_STATE_CHANGED, provisioned));
             }
         };
-        mReceiverForRequestIsSatelliteAllowedForCurrentLocation = new ResultReceiver(this) {
-            @Override
-            protected void onReceiveResult(int resultCode, Bundle resultData) {
-                if (resultCode == SATELLITE_RESULT_SUCCESS) {
-                    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
@@ -187,6 +168,9 @@
             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);
                 break;
@@ -206,7 +190,7 @@
             return;
         }
 
-        /**
+        /*
          * 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
@@ -226,6 +210,7 @@
      */
     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");
@@ -245,13 +230,14 @@
             return;
         }
         if (mEmergencyConnection == null) {
-            mSatelliteController.requestIsSatelliteCommunicationAllowedForCurrentLocation(
-                    SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                    mReceiverForRequestIsSatelliteAllowedForCurrentLocation);
             handleStateChangedEventForHysteresisTimer();
             registerForInterestedStateChangedEvents();
         }
         mEmergencyConnection = connection;
+        synchronized (mLock) {
+            mCheckingAccessRestrictionInProgress = false;
+            mIsSatelliteAllowedForCurrentLocation = false;
+        }
     }
 
     private void handleSatelliteProvisionStateChangedEvent(boolean provisioned) {
@@ -261,32 +247,59 @@
     }
 
     private void handleTimeoutEvent() {
-        /**
-         * 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()
-                && mIsSatelliteAllowedInCurrentLocation.get()
-                && (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;
-
+        synchronized (mLock) {
+            mIsTimerTimedOut = true;
+            evaluateSendingConnectionEventDisplayEmergencyMessage();
         }
-        logd("handleTimeoutEvent: isImsRegistered=" + isImsRegistered()
-                + ", isCellularAvailable=" + isCellularAvailable()
-                + ", mIsSatelliteAllowedInCurrentLocation="
-                + mIsSatelliteAllowedInCurrentLocation.get()
-                + ", shouldTrackCall=" + shouldTrackCall(mEmergencyConnection.getState()));
-        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() {
@@ -296,8 +309,14 @@
         }
     }
 
-    private boolean isSatelliteViaOemAvailable() {
-        return mSatelliteController.isSatelliteViaOemProvisioned();
+    /**
+     * Check if satellite is available via OEM
+     * @return {@code true} if satellite is provisioned via OEM else return {@code false}
+     */
+    @VisibleForTesting
+    public boolean isSatelliteViaOemAvailable() {
+        Boolean satelliteProvisioned = mSatelliteController.isSatelliteViaOemProvisioned();
+        return satelliteProvisioned != null ? satelliteProvisioned : false;
     }
 
     private boolean isSatelliteViaCarrierAvailable() {
@@ -317,7 +336,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.
@@ -329,6 +348,13 @@
         if (!shouldTrackCall(state)) {
             reportEsosRecommenderDecision(false);
             cleanUpResources();
+        } else {
+            // Location service will enter emergency mode only when connection state changes to
+            // STATE_DIALING
+            if (state == Connection.STATE_DIALING
+                    && mSatelliteController.isSatelliteSupportedViaOem()) {
+                requestIsSatelliteAllowedForCurrentLocation();
+            }
         }
     }
 
@@ -341,18 +367,22 @@
                         .setCellularServiceState(getBestCellularServiceState())
                         .setIsMultiSim(isMultiSim())
                         .setRecommendingHandoverType(getEmergencyCallToSatelliteHandoverType())
-                        .setIsSatelliteAllowedInCurrentLocation(
-                                mIsSatelliteAllowedInCurrentLocation.get())
+                        .setIsSatelliteAllowedInCurrentLocation(isSatelliteAllowed())
                         .build());
     }
 
     private void cleanUpResources() {
-        stopTimer();
-        if (mEmergencyConnection != null) {
-            unregisterForInterestedStateChangedEvents();
+        synchronized (mLock) {
+            stopTimer();
+            if (mEmergencyConnection != null) {
+                unregisterForInterestedStateChangedEvents();
+            }
+            mEmergencyConnection = null;
+            mCountOfTimerStarted = 0;
+            mIsTimerTimedOut = false;
+            mCheckingAccessRestrictionInProgress = false;
+            mIsSatelliteAllowedForCurrentLocation = false;
         }
-        mEmergencyConnection = null;
-        mCountOfTimerStarted = 0;
     }
 
     private void registerForInterestedStateChangedEvents() {
@@ -450,15 +480,28 @@
     }
 
     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 void handleSatelliteAccessRestrictionCheckingResult(boolean satelliteAllowed) {
+        synchronized (mLock) {
+            mIsSatelliteAllowedForCurrentLocation = satelliteAllowed;
+            mCheckingAccessRestrictionInProgress = false;
+            evaluateSendingConnectionEventDisplayEmergencyMessage();
+        }
     }
 
     private static long getEmergencyCallWaitForConnectionTimeoutMillis(@NonNull Context context) {
@@ -627,6 +670,42 @@
         }
     }
 
+    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));
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
index d0497eb..5c79c28 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -40,7 +40,7 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 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;
@@ -130,7 +130,7 @@
     private long mSatelliteStayAtListeningFromSendingMillis;
     private long mSatelliteStayAtListeningFromReceivingMillis;
     private long mSatelliteNbIotInactivityTimeoutMillis;
-    private final ConcurrentHashMap<IBinder, ISatelliteStateCallback> mListeners;
+    private final ConcurrentHashMap<IBinder, ISatelliteModemStateCallback> mListeners;
     @SatelliteManager.SatelliteModemState private int mCurrentState;
     final boolean mIsSatelliteSupported;
     private boolean mIsDemoMode = false;
@@ -272,7 +272,8 @@
      *
      * @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);
@@ -286,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());
     }
 
@@ -356,6 +358,7 @@
         }
         return true;
     }
+
     /**
      * Adjusts listening timeout duration when demo mode is on
      *
@@ -709,6 +712,9 @@
                     && datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE) {
                 startNbIotInactivityTimer();
+            } else if (isSending(datagramTransferState.sendState)
+                    || isReceiving(datagramTransferState.receiveState)) {
+                restartNbIotInactivityTimer();
             }
         }
     }
@@ -759,9 +765,8 @@
 
         private void handleEventDatagramTransferStateChanged(
                 @NonNull DatagramTransferState datagramTransferState) {
-            if (datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING
-                    || datagramTransferState.receiveState
-                    == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING) {
+            if (isSending(datagramTransferState.sendState)
+                    || isReceiving(datagramTransferState.receiveState)) {
                 transitionTo(mTransferringState);
             }
         }
@@ -809,7 +814,7 @@
     private void notifyStateChangedEvent(@SatelliteManager.SatelliteModemState int state) {
         mDatagramController.onSatelliteModemStateChanged(state);
 
-        List<ISatelliteStateCallback> toBeRemoved = new ArrayList<>();
+        List<ISatelliteModemStateCallback> toBeRemoved = new ArrayList<>();
         mListeners.values().forEach(listener -> {
             try {
                 listener.onSatelliteModemStateChanged(state);
@@ -972,6 +977,11 @@
                 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");
diff --git a/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
index f1845c5..8591e86 100644
--- a/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
+++ b/src/java/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifier.java
@@ -16,58 +16,161 @@
 
 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.internal.telephony.metrics.CellularSecurityTransparencyStats;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 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.
+ * disclosed in the clear. Callers add CellularIdentifierDisclosure instances by calling
+ * addDisclosure.
  *
- * <p>This class will either emit notifications through SafetyCenterManager if SafetyCenter exists
- * on a device, or it will emit system notifications otherwise.
+ * <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;
 
-    @VisibleForTesting
-    public CellularIdentifierDisclosureNotifier() {}
+    // 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;
+    private SubscriptionManagerService mSubscriptionManagerService;
+    private CellularSecurityTransparencyStats mCellularSecurityTransparencyStats;
 
-    /**
-     * Add a CellularIdentifierDisclosure to be tracked by this instance.
-     * If appropriate, this will trigger a user notification.
-     */
-    public void addDisclosure(CellularIdentifierDisclosure disclosure) {
-        // TODO (b/308985417) this is a stub method for now. Logic
-        // for tracking disclosures and emitting notifications will flow
-        // from here.
-        Rlog.d(TAG, "Identifier disclosure reported: " + disclosure);
+    public CellularIdentifierDisclosureNotifier(CellularNetworkSecuritySafetySource safetySource) {
+        this(Executors.newSingleThreadScheduledExecutor(), DEFAULT_WINDOW_CLOSE_DURATION_IN_MINUTES,
+                TimeUnit.MINUTES, safetySource, SubscriptionManagerService.getInstance(),
+                new CellularSecurityTransparencyStats());
     }
 
     /**
-     * Get a singleton CellularIdentifierDisclosureNotifier.
+     * Construct a CellularIdentifierDisclosureNotifier by injection. This should only be used for
+     * testing.
+     *
+     * @param notificationQueue a ScheduledExecutorService that should only execute on a single
+     *     thread.
      */
-    public static synchronized CellularIdentifierDisclosureNotifier getInstance() {
-        if (sInstance == null) {
-            sInstance = new CellularIdentifierDisclosureNotifier();
-        }
+    @VisibleForTesting
+    public CellularIdentifierDisclosureNotifier(
+            ScheduledExecutorService notificationQueue,
+            long windowCloseDuration,
+            TimeUnit windowCloseUnit,
+            CellularNetworkSecuritySafetySource safetySource,
+            SubscriptionManagerService subscriptionManagerService,
+            CellularSecurityTransparencyStats cellularSecurityTransparencyStats) {
+        mSerializedWorkQueue = notificationQueue;
+        mWindowCloseDuration = windowCloseDuration;
+        mWindowCloseUnit = windowCloseUnit;
+        mWindows = new HashMap<>();
+        mSafetySource = safetySource;
+        mSubscriptionManagerService = subscriptionManagerService;
+        mCellularSecurityTransparencyStats = cellularSecurityTransparencyStats;
+    }
 
-        return sInstance;
+    /**
+     * 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);
+
+        logDisclosure(subId, 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
+    }
+
+    private void logDisclosure(int subId, CellularIdentifierDisclosure disclosure) {
+        try {
+            mSerializedWorkQueue.execute(runLogDisclosure(subId, disclosure));
+        } catch (RejectedExecutionException e) {
+            Rlog.e(TAG, "Failed to schedule runLogDisclosure: " + e.getMessage());
+        }
+    }
+
+    private Runnable runLogDisclosure(int subId,
+            CellularIdentifierDisclosure disclosure) {
+        return () -> {
+            SubscriptionInfoInternal subInfo =
+                    mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
+            String mcc = null;
+            String mnc = null;
+            if (subInfo != null) {
+                mcc = subInfo.getMcc();
+                mnc = subInfo.getMnc();
+            }
+
+            mCellularSecurityTransparencyStats.logIdentifierDisclosure(disclosure, mcc, mnc,
+                    isEnabled());
+        };
     }
 
     /**
      * Re-enable if previously disabled. This means that {@code addDisclsoure} will start tracking
      * disclosures again and potentially emitting notifications.
      */
-    public void enable() {
-        Rlog.d(TAG, "enabled");
-        mEnabled = true;
+    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());
+            }
+        }
     }
 
     /**
@@ -75,12 +178,220 @@
      * 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() {
+    public void disable(Context context) {
         Rlog.d(TAG, "disabled");
-        mEnabled = false;
+        synchronized (mEnabledLock) {
+            mEnabled = false;
+            try {
+                mSerializedWorkQueue.execute(onDisableNotifier(context));
+            } catch (RejectedExecutionException e) {
+                Rlog.e(TAG, "Failed to schedule onDisableNotifier: " + e.getMessage());
+            }
+        }
     }
 
     public boolean isEnabled() {
-        return mEnabled;
+        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..3ece701
--- /dev/null
+++ b/src/java/com/android/internal/telephony/security/NullCipherNotifier.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.security;
+
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UNKNOWN;
+
+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 android.annotation.IntDef;
+import android.content.Context;
+import android.telephony.SecurityAlgorithmUpdate;
+import android.telephony.SecurityAlgorithmUpdate.ConnectionEvent;
+import android.telephony.SecurityAlgorithmUpdate.SecurityAlgorithm;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * 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.
+ *
+ * TODO(b/319662115): handle radio availability, no service, SIM removal, etc.
+ *
+ * @hide
+ */
+public class NullCipherNotifier {
+    private static final String TAG = "NullCipherNotifier";
+    private static NullCipherNotifier sInstance;
+
+    private final CellularNetworkSecuritySafetySource mSafetySource;
+    private final HashMap<Integer, SubscriptionState> mSubscriptionState = new HashMap<>();
+
+    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;
+
+    /**
+     * Gets a singleton NullCipherNotifier.
+     */
+    public static synchronized NullCipherNotifier getInstance(
+            CellularNetworkSecuritySafetySource safetySource) {
+        if (sInstance == null) {
+            sInstance = new NullCipherNotifier(
+                    Executors.newSingleThreadScheduledExecutor(),
+                    safetySource);
+        }
+        return sInstance;
+    }
+
+    @VisibleForTesting
+    public NullCipherNotifier(
+            ScheduledExecutorService notificationQueue,
+            CellularNetworkSecuritySafetySource safetySource) {
+        mSerializedWorkQueue = notificationQueue;
+        mSafetySource = safetySource;
+    }
+
+    /**
+     * Adds a security algorithm update. If appropriate, this will trigger a user notification.
+     */
+    public void onSecurityAlgorithmUpdate(
+            Context context, int subId, SecurityAlgorithmUpdate update) {
+        Rlog.d(TAG, "Security algorithm update: subId = " + subId + " " + update);
+
+        if (shouldIgnoreUpdate(update)) {
+            return;
+        }
+
+        try {
+            mSerializedWorkQueue.execute(() -> {
+                SubscriptionState subState = mSubscriptionState.get(subId);
+                if (subState == null) {
+                    subState = new SubscriptionState();
+                    mSubscriptionState.put(subId, subState);
+                }
+
+                @CellularNetworkSecuritySafetySource.NullCipherState int nullCipherState =
+                        subState.update(update);
+                mSafetySource.setNullCipherState(context, subId, nullCipherState);
+            });
+        } catch (RejectedExecutionException e) {
+            Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Enables null cipher notification; {@code onSecurityAlgorithmUpdate} will start handling
+     * security algorithm updates and send notifications to the user when required.
+     */
+    public void enable(Context context) {
+        synchronized (mEnabledLock) {
+            Rlog.d(TAG, "enabled");
+            mEnabled = true;
+            scheduleOnEnabled(context, 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(Context context) {
+        synchronized (mEnabledLock) {
+            Rlog.d(TAG, "disabled");
+            mEnabled = false;
+            scheduleOnEnabled(context, false);
+        }
+    }
+
+    /** Checks whether the null cipher notification is enabled. */
+    public boolean isEnabled() {
+        synchronized (mEnabledLock) {
+            return mEnabled;
+        }
+    }
+
+    private void scheduleOnEnabled(Context context, boolean enabled) {
+        try {
+            mSerializedWorkQueue.execute(() -> {
+                Rlog.i(TAG, "On enable notifier. Enable value: " + enabled);
+                mSafetySource.setNullCipherIssueEnabled(context, enabled);
+            });
+        } catch (RejectedExecutionException e) {
+            Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage());
+        }
+
+    }
+
+    /** Returns whether the update should be dropped and the monitoring state left unchanged. */
+    private static boolean shouldIgnoreUpdate(SecurityAlgorithmUpdate update) {
+        // Ignore emergencies.
+        if (update.isUnprotectedEmergency()) {
+            return true;
+        }
+
+        switch (update.getConnectionEvent()) {
+            // Ignore non-link layer protocols. Monitoring is only looking for data exposed
+            // over-the-air so only the link layer protocols are tracked. Higher-level protocols can
+            // protect data further into the network but that is out of scope.
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP:
+            // Ignore emergencies.
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP_SOS:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP_SOS:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP_SOS:
+            case SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP_SOS:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Determines whether an algorithm does not attempt to implement any encryption and is therefore
+     * considered a null cipher.
+     *
+     * <p>Only the algorithms known to be null ciphers are classified as such. Explicitly unknown
+     * algorithms, or algorithms that are unknown by means of values added to newer HALs, are
+     * assumed not to be null ciphers.
+     */
+    private static boolean isNullCipher(@SecurityAlgorithm int algorithm) {
+        switch (algorithm) {
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A50:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA0:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA0:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA0:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA0:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_IMS_NULL:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SIP_NULL:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_NULL:
+            case SecurityAlgorithmUpdate.SECURITY_ALGORITHM_OTHER:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /** The state of network connections for a subscription. */
+    private static final class SubscriptionState {
+        private @NetworkClass int mActiveNetworkClass = NETWORK_CLASS_UNKNOWN;
+        private final HashMap<Integer, ConnectionState> mState = new HashMap<>();
+
+        private @CellularNetworkSecuritySafetySource.NullCipherState int
+                update(SecurityAlgorithmUpdate update) {
+            boolean fromNullCipherState = hasNullCipher();
+
+            @NetworkClass int networkClass = getNetworkClass(update.getConnectionEvent());
+            if (networkClass != mActiveNetworkClass || networkClass == NETWORK_CLASS_UNKNOWN) {
+                mState.clear();
+                mActiveNetworkClass = networkClass;
+            }
+
+            ConnectionState fromState =
+                    mState.getOrDefault(update.getConnectionEvent(), ConnectionState.UNKNOWN);
+            ConnectionState toState = new ConnectionState(
+                    update.getEncryption() == SECURITY_ALGORITHM_UNKNOWN
+                            ? fromState.getEncryption()
+                            : update.getEncryption(),
+                    update.getIntegrity() == SECURITY_ALGORITHM_UNKNOWN
+                            ? fromState.getIntegrity()
+                            : update.getIntegrity());
+            mState.put(update.getConnectionEvent(), toState);
+
+            if (hasNullCipher()) {
+                return NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED;
+            }
+            if (!fromNullCipherState || mActiveNetworkClass == NETWORK_CLASS_UNKNOWN) {
+                return NULL_CIPHER_STATE_ENCRYPTED;
+            }
+            return NULL_CIPHER_STATE_NOTIFY_ENCRYPTED;
+        }
+
+        private boolean hasNullCipher() {
+            return mState.values().stream().anyMatch(ConnectionState::hasNullCipher);
+        }
+
+        private static final int NETWORK_CLASS_UNKNOWN = 0;
+        private static final int NETWORK_CLASS_2G = 2;
+        private static final int NETWORK_CLASS_3G = 3;
+        private static final int NETWORK_CLASS_4G = 4;
+        private static final int NETWORK_CLASS_5G = 5;
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"NETWORK_CLASS_"}, value = {NETWORK_CLASS_UNKNOWN,
+                NETWORK_CLASS_2G, NETWORK_CLASS_3G, NETWORK_CLASS_4G,
+                NETWORK_CLASS_5G})
+        private @interface NetworkClass {}
+
+        private static @NetworkClass int getNetworkClass(
+                @ConnectionEvent int connectionEvent) {
+            switch (connectionEvent) {
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_GSM:
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_GPRS:
+                    return NETWORK_CLASS_2G;
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_3G:
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_3G:
+                    return NETWORK_CLASS_3G;
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_LTE:
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_LTE:
+                    return NETWORK_CLASS_4G;
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_5G:
+                case SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G:
+                    return NETWORK_CLASS_5G;
+                default:
+                    return NETWORK_CLASS_UNKNOWN;
+            }
+        }
+    }
+
+    /** The state of security algorithms for a network connection. */
+    private static final class ConnectionState {
+        private static final ConnectionState UNKNOWN =
+                 new ConnectionState(SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_UNKNOWN);
+
+        private final @SecurityAlgorithm int mEncryption;
+        private final @SecurityAlgorithm int mIntegrity;
+
+        private ConnectionState(
+                @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity) {
+            mEncryption = encryption;
+            mIntegrity = integrity;
+        }
+
+        private @SecurityAlgorithm int getEncryption() {
+            return mEncryption;
+        }
+
+        private @SecurityAlgorithm int getIntegrity() {
+            return mIntegrity;
+        }
+
+        private boolean hasNullCipher() {
+            return isNullCipher(mEncryption) || isNullCipher(mIntegrity);
+        }
+    };
+}
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
index 7b927f2..3d07d47 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -281,7 +281,19 @@
                     SubscriptionInfoInternal::getSatelliteAttachEnabledForCarrier),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_IS_NTN,
-                    SubscriptionInfoInternal::getOnlyNonTerrestrialNetwork)
+                    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)
     );
 
     /**
@@ -412,7 +424,16 @@
                     SubscriptionDatabaseManager::setSatelliteAttachEnabledForCarrier),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_IS_NTN,
-                    SubscriptionDatabaseManager::setNtn)
+                    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)
     );
 
     /**
@@ -474,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)
     );
 
     /**
@@ -2056,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 {
@@ -2065,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);
     }
 
     /**
@@ -2302,11 +2390,24 @@
                         SimInfo.COLUMN_SATELLITE_ENABLED)))
                 .setSatelliteAttachEnabledForCarrier(cursor.getInt(
                         cursor.getColumnIndexOrThrow(
-                                SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER)));
+                                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();
     }
 
@@ -2381,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 f08a659..82af4e8 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
@@ -444,7 +444,7 @@
 
     /**
      * Whether satellite attach for carrier is enabled or disabled by user.
-     * By default, its disabled. It is intended to use integer to fit the database format.
+     * By default, its enabled. It is intended to use integer to fit the database format.
      */
     private final int mIsSatelliteAttachEnabledForCarrier;
 
@@ -469,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}.
@@ -543,6 +565,10 @@
         // 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;
     }
 
     /**
@@ -615,7 +641,8 @@
      * @return the number of this subscription.
      */
     public String getNumber() {
-        return mNumber;
+        if (TextUtils.isEmpty(mNumberFromCarrier)) return mNumber;
+        return mNumberFromCarrier;
     }
 
     /**
@@ -1193,6 +1220,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() {
@@ -1204,7 +1261,7 @@
                 .setCarrierName(mCarrierName)
                 .setDisplayNameSource(mDisplayNameSource)
                 .setIconTint(mIconTint)
-                .setNumber(mNumber)
+                .setNumber(getNumber())
                 .setDataRoaming(mDataRoaming)
                 .setMcc(mMcc)
                 .setMnc(mMnc)
@@ -1229,6 +1286,9 @@
                 .setPortIndex(mPortIndex)
                 .setUsageSetting(mUsageSetting)
                 .setOnlyNonTerrestrialNetwork(mIsOnlyNonTerrestrialNetwork == 1)
+                .setServiceCapabilities(
+                        SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities))
+                .setTransferStatus(mTransferStatus)
                 .build();
     }
 
@@ -1249,7 +1309,7 @@
                 + " displayNameSource="
                 + SubscriptionManager.displayNameSourceToString(mDisplayNameSource)
                 + " iconTint=" + mIconTint
-                + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
+                + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, getNumber())
                 + " dataRoaming=" + mDataRoaming
                 + " mcc=" + mMcc
                 + " mnc=" + mMnc
@@ -1288,6 +1348,10 @@
                 + " satellite_attach_enabled_for_carrier=" + mIsSatelliteAttachEnabledForCarrier
                 + " getOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
                 + " isGroupDisabled=" + mIsGroupDisabled
+                + " serviceCapabilities=" + mServiceCapabilities
+                + " transferStatus=" + mTransferStatus
+                + " satelliteEntitlementStatus=" + mIsSatelliteEntitlementStatus
+                + " satelliteEntitlementPlmns=" + mSatelliteEntitlementPlmns
                 + "]";
     }
 
@@ -1344,7 +1408,11 @@
                 that.mDeviceToDeviceStatusSharingContacts) && mNumberFromCarrier.equals(
                 that.mNumberFromCarrier) && mNumberFromIms.equals(that.mNumberFromIms)
                 && mIsSatelliteAttachEnabledForCarrier == that.mIsSatelliteAttachEnabledForCarrier
-                && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork;
+                && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
+                && mServiceCapabilities == that.mServiceCapabilities
+                && mTransferStatus == that.mTransferStatus
+                && mIsSatelliteEntitlementStatus == that.mIsSatelliteEntitlementStatus
+                && mSatelliteEntitlementPlmns == that.mSatelliteEntitlementPlmns;
     }
 
     @Override
@@ -1366,7 +1434,9 @@
                 mNumberFromCarrier,
                 mNumberFromIms, mPortIndex, mUsageSetting, mLastUsedTPMessageReference, mUserId,
                 mIsSatelliteEnabled, mCardId, mIsGroupDisabled,
-                mIsSatelliteAttachEnabledForCarrier, mIsOnlyNonTerrestrialNetwork);
+                mIsSatelliteAttachEnabledForCarrier, mIsOnlyNonTerrestrialNetwork,
+                mServiceCapabilities, mTransferStatus, mIsSatelliteEntitlementStatus,
+                mSatelliteEntitlementPlmns);
         result = 31 * result + Arrays.hashCode(mNativeAccessRules);
         result = 31 * result + Arrays.hashCode(mCarrierConfigAccessRules);
         result = 31 * result + Arrays.hashCode(mRcsConfig);
@@ -1732,7 +1802,7 @@
         /**
          * Whether satellite attach for carrier is enabled by user.
          */
-        private int mIsSatelliteAttachEnabledForCarrier = 0;
+        private int mIsSatelliteAttachEnabledForCarrier = 1;
 
         /**
          * Whether this subscription is used for communicating with non-terrestrial network or not.
@@ -1754,6 +1824,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() {
@@ -1831,6 +1922,10 @@
             // 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;
         }
 
         /**
@@ -2753,6 +2848,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 a8d05a3..8757c97 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -16,9 +16,13 @@
 
 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;
@@ -183,7 +187,9 @@
             SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
             SimInfo.COLUMN_SATELLITE_ENABLED,
             SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
-            SimInfo.COLUMN_IS_NTN
+            SimInfo.COLUMN_IS_NTN,
+            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS
     );
 
     /**
@@ -249,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.
@@ -455,6 +465,7 @@
         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);
@@ -914,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.
@@ -1162,6 +1193,17 @@
                         builder.setDisplayNameSource(SubscriptionManager.NAME_SOURCE_CARRIER);
                     }
 
+                    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) {
@@ -1186,11 +1228,9 @@
                         String mnc = cid.getMnc();
                         builder.setMcc(mcc);
                         builder.setMnc(mnc);
-                        if (mFeatureFlags.oemEnabledSatelliteFlag()) {
+                        if (mFeatureFlags.oemEnabledSatelliteFlag() && !isSatelliteSpn) {
                             builder.setOnlyNonTerrestrialNetwork(
                                     isSatellitePlmn(mcc + mnc) ? 1 : 0);
-                        } else {
-                            log("updateEmbeddedSubscriptions: oemEnabledSatelliteFlag is disabled");
                         }
                     }
                     // If cardId = unsupported or un-initialized, we have no reason to update DB.
@@ -1202,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);
@@ -1463,7 +1507,6 @@
                             MccTable.updateMccMncConfiguration(mContext, mccMnc);
                         }
                         setMccMnc(subId, mccMnc);
-                        setNtn(subId, isSatellitePlmn(mccMnc));
                     } else {
                         loge("updateSubscription: mcc/mnc is empty");
                     }
@@ -1700,6 +1743,39 @@
                             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);
+            }
+        }
     }
 
     /**
@@ -1744,6 +1820,9 @@
             throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
                     + "carrier privilege");
         }
+
+        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.
@@ -1786,6 +1865,8 @@
                     + "carrier privilege");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfo");
+
         SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
                 .getSubscriptionInfoInternal(subId);
         if (subInfo != null && subInfo.isActive()) {
@@ -1814,6 +1895,8 @@
         enforcePermissions("getActiveSubscriptionInfoForIccId",
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfoForIccId");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             iccId = IccUtils.stripTrailingFs(iccId);
@@ -1858,6 +1941,9 @@
 
         }
 
+        enforceTelephonyFeatureWithException(callingPackage,
+                "getActiveSubscriptionInfoForSimSlotIndex");
+
         if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
             throw new IllegalArgumentException("Invalid slot index " + slotIndex);
         }
@@ -1907,10 +1993,11 @@
                     + "permission. Returning empty list here.");
             return Collections.emptyList();
         }
-        if (isForAllProfiles && !hasAcrossAllUsersPermission()) {
-            //TODO(b/308809058 to determine whether the permission enforcement is needed)
-            loge("getActiveSubscriptionInfoList: "
-                    + callingPackage + " has no appropriate permission.");
+
+        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfoList");
+
+        if (isForAllProfiles) {
+            enforcePermissionAccessAllUserProfiles();
         }
         return getSubscriptionInfoStreamAsUser(isForAllProfiles
                 ? UserHandle.ALL : BINDER_WRAPPER.getCallingUserHandle())
@@ -1951,18 +2038,21 @@
             throw new SecurityException("Need READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE, or "
                     + "carrier privilege");
         }
-        if (isForAllProfiles && !hasAcrossAllUsersPermission()) {
-            //TODO(b/308809058 to determine whether the permission enforcement is needed)
-            loge("getActiveSubInfoCount: "
-                    + callingPackage + " has no appropriate permission.");
+        if (isForAllProfiles) {
+            enforcePermissionAccessAllUserProfiles();
         }
+
+        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubInfoCount");
+
         return getActiveSubIdListAsUser(false, isForAllProfiles
                 ? UserHandle.ALL : BINDER_WRAPPER.getCallingUserHandle()).length;
     }
 
-    /**@return {@code true} if the caller is permitted to see all subscriptions. */
-    private boolean hasAcrossAllUsersPermission() {
-        return hasPermissions(Manifest.permission.INTERACT_ACROSS_USERS,
+    /** @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);
     }
@@ -1995,6 +2085,9 @@
             @Nullable String callingFeatureId) {
         enforcePermissions("getAvailableSubscriptionInfoList",
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+
+        enforceTelephonyFeatureWithException(callingPackage, "getAvailableSubscriptionInfoList");
+
         return getAvailableSubscriptionsInternalStream()
                 .sorted(Comparator.comparing(SubscriptionInfoInternal::getSimSlotIndex)
                         .thenComparing(SubscriptionInfoInternal::getSubscriptionId))
@@ -2079,6 +2172,8 @@
      */
     @Override
     public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
+        enforcePermissions("requestEmbeddedSubscriptionInfoListRefresh",
+                Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS);
         updateEmbeddedSubscriptions(List.of(cardId), null);
     }
 
@@ -2093,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)
@@ -2104,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 {
@@ -2159,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
@@ -2382,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);
@@ -2434,6 +2542,8 @@
                     + " carrier privilege permission on all specified subscriptions");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "createSubscriptionGroup");
+
         long identity = Binder.clearCallingIdentity();
 
         try {
@@ -2470,6 +2580,10 @@
             @Nullable ISetOpportunisticDataCallback callback) {
         enforcePermissions("setPreferredDataSubscriptionId",
                 Manifest.permission.MODIFY_PHONE_STATE);
+
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "setPreferredDataSubscriptionId");
+
         final long token = Binder.clearCallingIdentity();
 
         try {
@@ -2556,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.
@@ -2608,6 +2724,8 @@
             throw new IllegalArgumentException("subIdList is empty.");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "removeSubscriptionsFromGroup");
+
         long identity = Binder.clearCallingIdentity();
 
         try {
@@ -2690,6 +2808,8 @@
                     + " permissions on subscriptions and the group.");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "addSubscriptionsIntoGroup");
+
         long identity = Binder.clearCallingIdentity();
 
         try {
@@ -2759,6 +2879,8 @@
             }
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getSubscriptionsInGroup");
+
         return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
                 .map(SubscriptionInfoInternal::toSubscriptionInfo)
                 .filter(info -> groupUuid.equals(info.getGroupUuid())
@@ -2869,6 +2991,9 @@
      */
     @Override
     public int getDefaultSubIdAsUser(@UserIdInt int userId) {
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "getDefaultVoiceSubIdAsUser");
+
         return getDefaultAsUser(userId, mDefaultSubId.get());
     }
 
@@ -2879,6 +3004,7 @@
      * @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))
@@ -2944,6 +3070,8 @@
             throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUBSCRIPTION_ID");
         }
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "setDefaultDataSubId");
+
         final long token = Binder.clearCallingIdentity();
         try {
             if (mDefaultDataSubId.set(subId)) {
@@ -3012,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)) {
@@ -3072,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)) {
@@ -3110,6 +3242,9 @@
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public int[] getActiveSubIdList(boolean visibleOnly) {
         enforcePermissions("getActiveSubIdList", Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "getActiveSubIdList");
+
         // UserHandle.ALL because this API is exposed as system API.
         return getActiveSubIdListAsUser(visibleOnly, UserHandle.ALL);
     }
@@ -3221,6 +3356,8 @@
                     + "accessed through getSubscriptionProperty.");
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "getSubscriptionProperty");
+
         final long token = Binder.clearCallingIdentity();
         try {
             Object value = mSubscriptionDatabaseManager.getSubscriptionProperty(subId, columnName);
@@ -3265,6 +3402,8 @@
             throw new IllegalArgumentException("Invalid subscription id " + subId);
         }
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "isSubscriptionEnabled");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
@@ -3294,6 +3433,8 @@
             throw new IllegalArgumentException("Invalid slot index " + slotIndex);
         }
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(), "getEnabledSubscriptionId");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
@@ -3330,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
@@ -3387,6 +3531,9 @@
         enforcePermissions("canDisablePhysicalSubscription",
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "canDisablePhysicalSubscription");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             Phone phone = PhoneFactory.getDefaultPhone();
@@ -3420,6 +3567,9 @@
         logl("setUiccApplicationsEnabled: subId=" + subId + ", enabled=" + enabled
                 + ", calling package=" + getCallingPackage());
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "setUiccApplicationsEnabled");
+
         final long identity = Binder.clearCallingIdentity();
         try {
 
@@ -3462,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
@@ -3494,6 +3647,9 @@
         enforcePermissions("setDeviceToDeviceStatusSharingContacts",
                 Manifest.permission.MODIFY_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "setDeviceToDeviceStatusSharingContacts");
+
         final long identity = Binder.clearCallingIdentity();
         try {
             Objects.requireNonNull(contacts, "contacts");
@@ -3561,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
@@ -3621,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);
@@ -3668,6 +3829,8 @@
                     + SubscriptionManager.phoneNumberSourceToString(source));
         }
 
+        enforceTelephonyFeatureWithException(callingPackage, "setPhoneNumber");
+
         Objects.requireNonNull(number, "number");
 
         final long identity = Binder.clearCallingIdentity();
@@ -3781,12 +3944,31 @@
     }
 
     /**
+     * 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.
@@ -3797,6 +3979,12 @@
             @NonNull UserHandle userHandle) {
         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.
@@ -3806,7 +3994,7 @@
                             + subscriptionId);
         }
 
-        if (mFeatureFlags.workProfileApiSplit()) {
+        if (mFeatureFlags.enforceSubscriptionUserFilter()) {
             return isSubscriptionAssociatedWithUserInternal(
                     subInfoInternal, userHandle.getIdentifier());
         }
@@ -3835,15 +4023,15 @@
      */
     private boolean isSubscriptionAssociatedWithUserInternal(
             @NonNull SubscriptionInfoInternal subInfo, @UserIdInt int userId) {
-        if (!mFeatureFlags.workProfileApiSplit()
+        if (!mFeatureFlags.enforceSubscriptionUserFilter()
                 || !CompatChanges.isChangeEnabled(FILTER_ACCESSIBLE_SUBS_BY_USER,
                 Binder.getCallingUid())) {
             return true;
         }
-        return subInfo.getUserId() == userId
-                // Can access the unassociated sub if the user doesn't have its own.
-                || (subInfo.getUserId() == UserHandle.USER_NULL
+        // 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;
     }
 
@@ -3866,7 +4054,7 @@
         enforcePermissions("getSubscriptionInfoListAssociatedWithUser",
                 Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
 
-        if (mFeatureFlags.workProfileApiSplit()) {
+        if (mFeatureFlags.enforceSubscriptionUserFilter()) {
             return getSubscriptionInfoStreamAsUser(userHandle)
                     .map(SubscriptionInfoInternal::toSubscriptionInfo)
                     .collect(Collectors.toList());
@@ -3943,6 +4131,9 @@
         enforcePermissions("restoreAllSimSpecificSettingsFromBackup",
                 Manifest.permission.MODIFY_PHONE_STATE);
 
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                "restoreAllSimSpecificSettingsFromBackup");
+
         long token = Binder.clearCallingIdentity();
         try {
             Bundle bundle = new Bundle();
@@ -4151,9 +4342,10 @@
      */
     @VisibleForTesting
     public void updateGroupDisabled() {
-        List<SubscriptionInfoInternal> activeSubscriptions = mSubscriptionDatabaseManager
+        List<SubscriptionInfo> activeSubscriptions = mSubscriptionDatabaseManager
                 .getAllSubscriptions().stream()
                 .filter(SubscriptionInfoInternal::isActive)
+                .map(SubscriptionInfoInternal::toSubscriptionInfo)
                 .collect(Collectors.toList());
         for (SubscriptionInfo oppSubInfo : getOpportunisticSubscriptions(
                 mContext.getOpPackageName(), mContext.getFeatureId())) {
@@ -4165,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.
      */
@@ -4178,7 +4468,7 @@
     /**
      * @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_esim_identifier", {@code false} otherwise.
+     * "config_satellite_sim_plmn_identifier", {@code false} otherwise.
      */
     private boolean isSatellitePlmn(@NonNull String mccMnc) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
@@ -4186,7 +4476,7 @@
             return false;
         }
 
-        final int id = R.string.config_satellite_sim_identifier;
+        final int id = R.string.config_satellite_sim_plmn_identifier;
         String overlayMccMnc = null;
         try {
             overlayMccMnc = mContext.getResources().getString(id);
@@ -4194,14 +4484,43 @@
             loge("isSatellitePlmn: id= " + id + ", ex=" + ex);
         }
         if (TextUtils.isEmpty(overlayMccMnc) && isMockModemAllowed()) {
-            log("isSatellitePlmn: Read config_satellite_sim_identifier from device config");
+            log("isSatellitePlmn: Read config_satellite_sim_plmn_identifier from device config");
             overlayMccMnc = DeviceConfig.getString(DeviceConfig.NAMESPACE_TELEPHONY,
-                    "config_satellite_sim_identifier", "");
+                    "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)
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 0459bf6..84e84d9 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -1799,6 +1799,8 @@
         }
         pw.decreaseIndent();
         pw.println();
+        mCarrierServiceBindHelper.dump(fd, pw, args);
+        pw.println();
         pw.println("sLocalLog= ");
         pw.increaseIndent();
         mPinStorage.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPort.java b/src/java/com/android/internal/telephony/uicc/UiccPort.java
index 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..a22f075 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;
@@ -1286,6 +1290,11 @@
             return -1;
         }
 
+        if (mUiccApplications[index] == null) {
+            loge("App index " + index + " is invalid");
+            return -1;
+        }
+
         if (mUiccApplications[index].getType() != expectedAppType
                 && mUiccApplications[index].getType() != altExpectedAppType) {
             loge("App index " + index + " is invalid since it's not "
diff --git a/tests/telephonytests/Android.bp b/tests/telephonytests/Android.bp
index 51ab617..8547581 100644
--- a/tests/telephonytests/Android.bp
+++ b/tests/telephonytests/Android.bp
@@ -42,6 +42,7 @@
         "testables",
         "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 2df688d..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;
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsMmTelManagerTests.java b/tests/telephonytests/src/android/telephony/ims/ImsMmTelManagerTests.java
index df53374..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;
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 e23a7f2..07482e0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
@@ -36,11 +36,12 @@
 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;
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 7345022..85c73a9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
@@ -43,10 +43,11 @@
 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;
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 722f6ac..6e6d4e4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java
@@ -416,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 85ea855..ebf1324 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
@@ -43,8 +43,9 @@
 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;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index da3920e..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;
@@ -310,6 +311,8 @@
                     return mNetworkPolicyManager;
                 case Context.TELEPHONY_IMS_SERVICE:
                     return mImsManager;
+                case Context.DEVICE_POLICY_SERVICE:
+                    return mDevicePolicyManager;
                 default:
                     return null;
             }
@@ -357,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);
         }
@@ -731,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
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index f7f67a5..d27ab98 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -33,7 +33,9 @@
 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;
@@ -379,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 018759a..c9e4c12 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
@@ -48,10 +48,11 @@
 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;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index 2692057..a13a92c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -130,8 +130,12 @@
                     + UserHandle.USER_NULL + ","
                     + Telephony.SimInfo.COLUMN_SATELLITE_ENABLED + " INTEGER DEFAULT 0,"
                     + Telephony.SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER
-                    + " INTEGER DEFAULT 0, "
-                    + Telephony.SimInfo.COLUMN_IS_NTN + " INTEGER DEFAULT 0"
+                    + " 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/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 369980c..7de75ae 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
@@ -33,12 +33,12 @@
 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;
 
@@ -75,7 +75,7 @@
         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
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 935da5a..4fce070 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -23,6 +23,7 @@
 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;
@@ -55,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;
@@ -77,17 +79,18 @@
 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;
@@ -692,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);
@@ -1481,6 +1510,44 @@
         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);
@@ -1491,6 +1558,7 @@
                 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();
@@ -1514,6 +1582,7 @@
                 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();
@@ -1525,9 +1594,6 @@
         processAllMessages();
 
         verify(mMockCi, times(1)).setN1ModeEnabled(eq(true), messageCaptor.capture());
-        AsyncResult.forMessage(messageCaptor.getValue(), Boolean.TRUE, null);
-        messageCaptor.getValue().sendToTarget();
-        processAllMessages();
     }
 
     @Test
@@ -1542,6 +1608,7 @@
         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();
@@ -1549,9 +1616,6 @@
         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());
-        AsyncResult.forMessage(messageCaptor.getValue(), null, null);
-        messageCaptor.getValue().sendToTarget();
-        processAllMessages();
     }
 
     @Test
@@ -1565,6 +1629,7 @@
                     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();
@@ -1613,9 +1678,6 @@
 
         verify(mMockCi, times(1)).isN1ModeEnabled(any()); // not called again
         verify(mMockCi, times(1)).setN1ModeEnabled(eq(true), messageCaptor.capture());
-        AsyncResult.forMessage(messageCaptor.getValue(), null, null);
-        messageCaptor.getValue().sendToTarget();
-        processAllMessages();
     }
 
     private void setupForWpsCallTest() throws Exception {
@@ -2761,7 +2823,7 @@
 
     @Test
     public void testCellularIdentifierDisclosureFlagOff() {
-        when(mFeatureFlags.enableIdentifierDisclosureTransparency()).thenReturn(false);
+        when(mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()).thenReturn(false);
 
         GsmCdmaPhone phoneUT =
                 new GsmCdmaPhone(
@@ -2783,7 +2845,7 @@
 
     @Test
     public void testCellularIdentifierDisclosureFlagOn() {
-        when(mFeatureFlags.enableIdentifierDisclosureTransparency()).thenReturn(true);
+        when(mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()).thenReturn(true);
 
         Phone phoneUT =
                 new GsmCdmaPhone(
@@ -2806,7 +2868,10 @@
 
     @Test
     public void testCellularIdentifierDisclosure_disclosureEventAddedToNotifier() {
-        when(mFeatureFlags.enableIdentifierDisclosureTransparency()).thenReturn(true);
+        int phoneId = 0;
+        int subId = 10;
+        when(mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()).thenReturn(true);
+        when(mSubscriptionManagerService.getSubId(phoneId)).thenReturn(subId);
 
         Phone phoneUT =
                 new GsmCdmaPhone(
@@ -2814,7 +2879,7 @@
                         mMockCi,
                         mNotifier,
                         true,
-                        0,
+                        phoneId,
                         PhoneConstants.PHONE_TYPE_GSM,
                         mTelephonyComponentFactory,
                         (c, p) -> mImsManager,
@@ -2832,20 +2897,23 @@
                         new AsyncResult(null, disclosure, null)));
         processAllMessages();
 
-        verify(mIdentifierDisclosureNotifier, times(1)).addDisclosure(eq(disclosure));
+        verify(mIdentifierDisclosureNotifier, times(1))
+                .addDisclosure(eq(mContext), eq(subId), eq(disclosure));
     }
 
     @Test
     public void testCellularIdentifierDisclosure_disclosureEventNull() {
-        when(mFeatureFlags.enableIdentifierDisclosureTransparency()).thenReturn(true);
-
+        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,
-                        0,
+                        phoneId,
                         PhoneConstants.PHONE_TYPE_GSM,
                         mTelephonyComponentFactory,
                         (c, p) -> mImsManager,
@@ -2857,7 +2925,7 @@
         processAllMessages();
 
         verify(mIdentifierDisclosureNotifier, never())
-                .addDisclosure(any(CellularIdentifierDisclosure.class));
+                .addDisclosure(eq(mContext), eq(subId), any(CellularIdentifierDisclosure.class));
     }
 
     @Test
@@ -2902,11 +2970,123 @@
         sendRadioAvailableToPhone(phoneUT);
         verify(mMockCi, times(1)).setCellularIdentifierTransparencyEnabled(anyBoolean(),
                 any(Message.class));
-        sendIdentifierDisclosureEnabledSuccessToPhone(phoneUT);
+        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(mContext), 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(eq(mContext));
+        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(eq(mContext));
+        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)));
@@ -2919,12 +3099,19 @@
         processAllMessages();
     }
 
-    private void sendIdentifierDisclosureEnabledSuccessToPhone(GsmCdmaPhone phone) {
-        phone.sendMessage(phone.obtainMessage(EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE,
-                new AsyncResult(null, null, null)));
+    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,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
index 3d4ef03..41fd45a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
@@ -16,9 +16,12 @@
 
 package com.android.internal.telephony;
 
+import android.content.pm.PackageManager;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.telephony.gsm.SmsMessage;
 import com.android.internal.util.HexDump;
@@ -27,6 +30,12 @@
 
 public class GsmSmsTest extends AndroidTestCase {
 
+    private boolean hasMessaging() {
+        final PackageManager pm = InstrumentationRegistry.getInstrumentation().getContext()
+                .getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING);
+    }
+
     @SmallTest
     public void testAddressing() throws Exception {
         String pdu = "07914151551512f2040B916105551511f100006060605130308A04D4F29C0E";
@@ -257,8 +266,8 @@
 
     @SmallTest
     public void testFragmentText() throws Exception {
-        boolean isGsmPhone = (TelephonyManager.getDefault().getPhoneType() ==
-                TelephonyManager.PHONE_TYPE_GSM);
+        boolean isGsmPhoneWithMessaging = (TelephonyManager.getDefault().getPhoneType()
+                == TelephonyManager.PHONE_TYPE_GSM) && hasMessaging();
 
         // Valid 160 character 7-bit text.
         String text = "123456789012345678901234567890123456789012345678901234567890" +
@@ -270,7 +279,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(0, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(1, fragments.size());
         }
@@ -285,7 +294,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(0, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(2, fragments.size());
             assertEquals(text, fragments.get(0) + fragments.get(1));
@@ -296,8 +305,8 @@
 
     @SmallTest
     public void testFragmentTurkishText() throws Exception {
-        boolean isGsmPhone = (TelephonyManager.getDefault().getPhoneType() ==
-                TelephonyManager.PHONE_TYPE_GSM);
+        boolean isGsmPhoneWithMessaging = (TelephonyManager.getDefault().getPhoneType()
+                == TelephonyManager.PHONE_TYPE_GSM) && hasMessaging();
 
         int[] oldTables = GsmAlphabet.getEnabledSingleShiftTables();
         int[] turkishTable = { 1 };
@@ -312,7 +321,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(1, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(1, fragments.size());
             assertEquals(text, fragments.get(0));
@@ -328,7 +337,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(1, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(2, fragments.size());
             assertEquals(text, fragments.get(0) + fragments.get(1));
@@ -346,7 +355,7 @@
         assertEquals(1, ted.codeUnitSize);
         assertEquals(0, ted.languageTable);
         assertEquals(1, ted.languageShiftTable);
-        if (isGsmPhone) {
+        if (isGsmPhoneWithMessaging) {
             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
             assertEquals(3, fragments.size());
             assertEquals(text, fragments.get(0) + fragments.get(1) + fragments.get(2));
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 7893e78..a7e9604 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;
@@ -247,11 +247,15 @@
             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 =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        mMultiSimSettingControllerUT = new MultiSimSettingController(mContext);
+        mMultiSimSettingControllerUT = new MultiSimSettingController(mContext, mFeatureFlags);
         processAllMessages();
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
@@ -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/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index 7f15af8..e08abd9 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,
@@ -1133,6 +1250,79 @@
     }
 
     @Test
+    public void testPrimaryTimerPrimaryCellChangeNrIdle() 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");
+        sendCarrierConfigChanged();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        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());
+
+        // change PCI during connected_rrc_idle
+        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());
+
+        // change PCI for the second time during connected_rrc_idle
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(3)
+                .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());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
     public void testSecondaryTimerExpire() throws Exception {
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
@@ -1317,6 +1507,344 @@
     }
 
     @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());
+
+        // empty PCC, 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());
+
+        // Verify secondary timer is still active after 6 seconds passed during
+        // connected_mmwave -> connected_rrc_idle secondary timer, should still keep the primary
+        // state icon.
+        moveTimeForward((5 + 1) * 1000);
+        processAllMessages();
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
+    public void testSecondaryTimerAdvanceBandReduceOnPciChange() throws Exception {
+        // The advance band secondary timer has been running for 6 seconds, 20 - 6 seconds are left.
+        testSecondaryTimerAdvanceBand();
+
+        // PCI changed from 1 to 2 for the first while the timer is running.
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, List.of(
+                        new PhysicalChannelConfig.Builder()
+                                .setPhysicalCellId(2)
+                                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                                .build()), null));
+        processAllMessages();
+
+        // Verify the first PCI change is exempted from triggering state change.
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // Verify the timer has been reduced from 20 - 6s(advance band) to 5s(regular).
+        moveTimeForward(5 * 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();
+        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());
+
+        // primary cell changes again
+        physicalChannelConfigs.clear();
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(3)
+                .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_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(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 +1894,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 +1960,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 4f2d8db..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,15 @@
 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;
@@ -52,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 {
@@ -63,8 +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 {
@@ -78,6 +104,8 @@
         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
@@ -129,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
@@ -163,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());
@@ -339,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 3c7e0b6..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;
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/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 c542857..121136d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -88,12 +88,12 @@
 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;
@@ -260,7 +260,8 @@
         mSatelliteController = Mockito.mock(SatelliteController.class);
         replaceInstance(SatelliteController.class, "sInstance", null,
                 mSatelliteController);
-        doReturn(new ArrayList<>()).when(mSatelliteController).getSatellitePlmnList(anyInt());
+        doReturn(new ArrayList<>()).when(mSatelliteController).getSatellitePlmnsForCarrier(
+                anyInt());
 
         mContextFixture.putResource(R.string.kg_text_message_separator, " \u2014 ");
 
@@ -3372,7 +3373,7 @@
         assertFalse(sst.mSS.isIwlanPreferred());
 
         when(mAccessNetworksManager.isAnyApnOnIwlan()).thenReturn(true);
-        accessNetworksManagerCallback.onPreferredTransportChanged(0);
+        accessNetworksManagerCallback.onPreferredTransportChanged(0, false);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         assertTrue(sst.mSS.isIwlanPreferred());
@@ -3386,7 +3387,8 @@
         CellIdentityGsm cellIdentity =
                 new CellIdentityGsm(0, 1, 900, 5, "101", "23", "test", "tst",
                         Collections.emptyList());
-        doReturn(Arrays.asList("10123")).when(mSatelliteController).getSatellitePlmnList(anyInt());
+        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 fa89082..7e4cb08 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
@@ -59,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;
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/SimultaneousCallingTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java
new file mode 100644
index 0000000..d3fde34
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java
@@ -0,0 +1,465 @@
+/*
+ * 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;
+
+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.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.ModemInfo;
+import android.telephony.PhoneCapability;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+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 com.android.internal.telephony.imsphone.ImsPhone;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SimultaneousCallingTrackerTest extends TelephonyTest {
+    // Mocked classes
+    Handler mHandler;
+    CommandsInterface mMockCi0;
+    CommandsInterface mMockCi1;
+    CommandsInterface mMockCi2;
+    private Phone mPhone1; // mPhone as phone 0 is already defined in TelephonyTest.
+    private Phone mPhone2;
+    private SubscriptionInfo mSubInfo;
+    private ImsPhone mImsPhone;
+    private ImsPhone mImsPhone1;
+    private ImsPhone mImsPhone2;
+    PhoneConfigurationManager.MockableInterface mMi;
+    private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 1;
+    private static final PhoneCapability STATIC_DSDA_CAPABILITY;
+    private static final Set<Integer> STATIC_SERVICE_CAPABILITIES;
+
+    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);
+
+        STATIC_SERVICE_CAPABILITIES = new HashSet<>(1);
+        STATIC_SERVICE_CAPABILITIES.add(SubscriptionManager.SERVICE_CAPABILITY_VOICE);
+    }
+    PhoneConfigurationManager mPcm;
+    SimultaneousCallingTracker mSct;
+
+    private FeatureFlags mFeatureFlags;
+    private TelephonyRegistryManager mMockRegistryManager;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mHandler = mock(Handler.class);
+        mMockCi0 = mock(CommandsInterface.class);
+        mMockCi1 = mock(CommandsInterface.class);
+        mMockCi2 = mock(CommandsInterface.class);
+        mFeatureFlags = mock(FeatureFlags.class);
+        mPhone1 = mock(Phone.class);
+        mPhone2 = mock(Phone.class);
+        mImsPhone = mock(ImsPhone.class);
+        mImsPhone1 = mock(ImsPhone.class);
+        mImsPhone2 = mock(ImsPhone.class);
+        mSubInfo = mock(SubscriptionInfo.class);
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(mImsPhone1).when(mPhone1).getImsPhone();
+        doReturn(mImsPhone2).when(mPhone2).getImsPhone();
+        mMi = mock(PhoneConfigurationManager.MockableInterface.class);
+        mPhone.mCi = mMockCi0;
+        mCT.mCi = mMockCi0;
+        mPhone1.mCi = mMockCi1;
+        mPhone2.mCi = mMockCi2;
+        doReturn(0).when(mPhone).getPhoneId();
+        doReturn(10).when(mPhone).getSubId();
+        doReturn(1).when(mPhone1).getPhoneId();
+        // This will be updated to 11 during each test in order to trigger onSubscriptionChanged:
+        doReturn(110).when(mPhone1).getSubId();
+        doReturn(2).when(mPhone2).getPhoneId();
+        doReturn(12).when(mPhone2).getSubId();
+        doReturn(STATIC_SERVICE_CAPABILITIES).when(mSubInfo).getServiceCapabilities();
+        doReturn(mSubInfo).when(mSubscriptionManagerService)
+                .getSubscriptionInfo(any(Integer.class));
+        doReturn(RIL.RADIO_HAL_VERSION_2_2).when(mMockRadioConfigProxy).getVersion();
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+        doReturn(true).when(mFeatureFlags).dataOnlyCellularService();
+        mMockRegistryManager = mContext.getSystemService(TelephonyRegistryManager.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mPcm = null;
+        mSct = null;
+        mPhone1 = null;
+        mPhone2 = null;
+        super.tearDown();
+    }
+
+    /**
+     * @param numOfSim the current number of SIM subscriptions
+     */
+    private void init(int numOfSim) throws Exception {
+        doReturn(numOfSim).when(mTelephonyManager).getActiveModemCount();
+        replaceInstance(SimultaneousCallingTracker.class, "sInstance", null, null);
+        replaceInstance(PhoneConfigurationManager.class, "sInstance", null, null);
+        switch (numOfSim) {
+            case 0 -> mPhones = new Phone[]{};
+            case 1 -> mPhones = new Phone[]{mPhone};
+            case 2 -> mPhones = new Phone[]{mPhone, mPhone1};
+            case 3 -> mPhones = new Phone[]{mPhone, mPhone1, mPhone2};
+        }
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        mPcm = PhoneConfigurationManager.init(mContext, mFeatureFlags);
+        mSct = SimultaneousCallingTracker.init(mContext, mFeatureFlags);
+        replaceInstance(PhoneConfigurationManager.class, "mMi", mPcm, mMi);
+        processAllMessages();
+    }
+
+    private void setRebootRequiredForConfigSwitch(boolean rebootRequired) {
+        doReturn(rebootRequired).when(mMi).isRebootRequiredForModemConfigChange();
+    }
+
+    private void setAndVerifyStaticCapability(PhoneCapability capability) {
+        mPcm.getCurrentPhoneCapability();
+        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());
+        assertTrue(mSct.isDeviceSimultaneousCallingCapable);
+
+    }
+
+    private void setAndVerifySlotsSupportingSimultaneousCellularCalling(int[] enabledLogicalSlots) {
+        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());
+    }
+
+    private void updateSubId(Phone phone, int newSubId) {
+        ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> cBCaptorList =
+                ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
+        verify(mMockRegistryManager, times(2))
+                .addOnSubscriptionsChangedListener(cBCaptorList.capture(), any());
+
+        // Change sub ID mapping
+        doReturn(newSubId).when(phone).getSubId();
+        List<SubscriptionManager.OnSubscriptionsChangedListener> listeners =
+                cBCaptorList.getAllValues();
+        listeners.get(0).onSubscriptionsChanged();
+        listeners.get(1).onSubscriptionsChanged();
+        processAllMessages();
+    }
+
+    /**
+     * Test that simultaneous calling is not supported when the device is only capable of a max
+     * active voice count of 1.
+     */
+    @Test
+    @SmallTest
+    public void testDeviceNotSimultaneousCallingCapable() throws Exception {
+        init(1);
+        assertFalse(mSct.isDeviceSimultaneousCallingCapable);
+        assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size());
+    }
+
+    /**
+     * Test that simultaneous calling is not supported when the subscriptions have different user
+     * associations.
+     */
+    @Test
+    @SmallTest
+    public void testDifferentUserAssociations_SimultaneousCallingDisabled() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+
+        //Assign each phone to a different user which should disable simultaneous calling:
+        doReturn(new UserHandle(123)).when(mPhone).getUserHandle();
+        doReturn(new UserHandle(321)).when(mPhone1).getUserHandle();
+
+        init(2);
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+
+        int[] enabledLogicalSlots = {0, 1};
+        setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
+
+        // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
+        updateSubId(mPhone1, 11);
+
+        assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size());
+        assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(11).size());
+    }
+
+    /**
+     * Test that simultaneous calling is not supported when IMS is not registered and cellular
+     * simultaneous calling is only supported for one SIM subscription.
+     */
+    @Test
+    @SmallTest
+    public void testCellularDSDANotSupported_SimultaneousCallingDisabled() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+
+        init(2);
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+
+        // Have the modem inform telephony that only phone slot 0 supports DSDA:
+        int[] enabledLogicalSlots = {0};
+        setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
+
+        // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
+        updateSubId(mPhone1, 11);
+
+        assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size());
+        assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(11).size());
+    }
+
+    /**
+     * Test that simultaneous calling is supported when IMS is not registered and cellular
+     * simultaneous calling is supported for both SIM subscription.
+     */
+    @Test
+    @SmallTest
+    public void testCellularDSDASupported_SimultaneousCallingEnabled() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+
+        init(2);
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+
+        int[] enabledLogicalSlots = {0, 1};
+        setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
+
+        // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
+        updateSubId(mPhone1, 11);
+
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10).contains(11));
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11).contains(10));
+    }
+
+    /**
+     * Test that simultaneous calling is supported when IMS is not registered and cellular
+     * simultaneous calling is supported for both SIM subscription. Then test that simultaneous
+     * calling is not supported after a multi SIM config change to single-SIM.
+     */
+    @Test
+    @SmallTest
+    public void testSingleSimSwitch_SimultaneousCallingDisabled() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+
+        init(2);
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+
+        int[] enabledLogicalSlots = {0, 1};
+        setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
+
+        // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
+        updateSubId(mPhone1, 11);
+
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10).contains(11));
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11).contains(10));
+
+        // Register for multi SIM config change.
+        mPcm.registerForMultiSimConfigChange(mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
+        verify(mHandler, never()).sendMessageAtTime(any(), anyLong());
+
+        // Switch to single sim.
+        setRebootRequiredForConfigSwitch(false);
+        mPcm.switchMultiSimConfig(1);
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).setNumOfLiveModems(eq(1), captor.capture());
+
+        // Send message back to indicate switch success.
+        Message message = captor.getValue();
+        AsyncResult.forMessage(message, null, null);
+        message.sendToTarget();
+        processAllMessages();
+
+        assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size());
+        assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(11).size());
+    }
+
+    /**
+     * Test that simultaneous calling is not supported when IMS is registered, vDSDA is disabled,
+     * but ImsService#CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING is not set.
+     */
+    @Test
+    @SmallTest
+    public void testImsDSDANotSupported_SimultaneousCallingDisabled() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+        doReturn(true).when(mPhone).isImsRegistered();
+        doReturn(true).when(mPhone1).isImsRegistered();
+        doReturn(false).when(mPhone)
+                .isImsServiceSimultaneousCallingSupportCapable(any(Context.class));
+        doReturn(false).when(mPhone1)
+                .isImsServiceSimultaneousCallingSupportCapable(any(Context.class));
+
+        init(2);
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+
+        int[] enabledLogicalSlots = {0, 1};
+        setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
+
+        // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
+        updateSubId(mPhone1, 11);
+
+        assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size());
+        assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(11).size());
+    }
+
+    /**
+     * Test that simultaneous calling is supported when IMS is registered, vDSDA is enabled,
+     * and the IMS transport type of each SIM subscription is WLAN.
+     */
+    //TODO: Implement a way to set vDSDAEnabled to true and then re-enable this test
+    @Ignore
+    @Test
+    @SmallTest
+    public void testImsVDSDAEnabledTransportTypeWLAN_SimultaneousCallingEnabled() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+        doReturn(true).when(mPhone).isImsRegistered();
+        doReturn(true).when(mPhone1).isImsRegistered();
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone).getTransportType();
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone1).getTransportType();
+
+        init(2);
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+
+        int[] enabledLogicalSlots = {0};
+        setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
+
+        // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
+        updateSubId(mPhone1, 11);
+
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10).contains(11));
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11).contains(10));
+    }
+
+    /**
+     * Test that simultaneous calling is supported when IMS is registered, vDSDA is disabled,
+     * ImsService#CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING is set and the IMS transport type of each
+     * SIM subscription is WLAN.
+     */
+    @Test
+    @SmallTest
+    public void testImsVDSDADisabledTransportTypeWLAN_SimultaneousCallingEnabled()
+            throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+        doReturn(true).when(mPhone).isImsRegistered();
+        doReturn(true).when(mPhone1).isImsRegistered();
+        doReturn(true).when(mPhone)
+                .isImsServiceSimultaneousCallingSupportCapable(any(Context.class));
+        doReturn(true).when(mPhone1)
+                .isImsServiceSimultaneousCallingSupportCapable(any(Context.class));
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone).getTransportType();
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone1).getTransportType();
+
+        init(2);
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+
+        int[] enabledLogicalSlots = {0, 1};
+        setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
+
+        // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
+        updateSubId(mPhone1, 11);
+
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10).contains(11));
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11).contains(10));
+    }
+
+    /**
+     * Test that simultaneous calling is supported between all subs in the following 3-SIM case:
+     * SIM A: IMS Unregistered, vDSDA Disabled
+     * SIM B: IMS WWAN Registered, vDSDA Disabled, CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING not set
+     * SIM C: IMS WLAN Registered, vDSDA Disabled, CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING set
+     */
+    @Test
+    @SmallTest
+    public void testThreeSimCase_SimultaneousCallingEnabled() throws Exception {
+        doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
+        doReturn(false).when(mPhone).isImsRegistered();
+        doReturn(true).when(mPhone1).isImsRegistered();
+        doReturn(true).when(mPhone2).isImsRegistered();
+        doReturn(true).when(mPhone1)
+                .isImsServiceSimultaneousCallingSupportCapable(any(Context.class));
+        doReturn(true).when(mPhone2)
+                .isImsServiceSimultaneousCallingSupportCapable(any(Context.class));
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mImsPhone1).getTransportType();
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone2).getTransportType();
+
+        init(3);
+        setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
+
+        int[] enabledLogicalSlots = {0, 1};
+        setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
+
+        // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
+        updateSubId(mPhone1, 11);
+
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10)
+                .containsAll(Arrays.asList(11,12)));
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11)
+                .containsAll(Arrays.asList(10,12)));
+        assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(12)
+                .containsAll(Arrays.asList(10,11)));
+    }
+}
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 dc63932..70e3d6b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
@@ -52,11 +52,12 @@
 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;
@@ -109,13 +110,18 @@
                     callingPkg, persistMessage, priority, expectMore, validityPeriod, messageId);
         }
 
-        public void testNotifySmsSentToEmergencyStateTracker(String destAddr, long messageId) {
-            notifySmsSentToEmergencyStateTracker(destAddr, messageId);
+        public void testNotifySmsSentToEmergencyStateTracker(String destAddr, long messageId,
+                boolean isOverIms) {
+            notifySmsSentToEmergencyStateTracker(destAddr, messageId, isOverIms);
         }
 
         public void testNotifySmsSentFailedToEmergencyStateTracker(String destAddr,
-                long messageId) {
-            notifySmsSentFailedToEmergencyStateTracker(destAddr, messageId);
+                long messageId, boolean isOverIms) {
+            notifySmsSentFailedToEmergencyStateTracker(destAddr, messageId, isOverIms);
+        }
+
+        public void testNotifySmsReceivedViaImsToEmergencyStateTracker(String origAddr) {
+            notifySmsReceivedViaImsToEmergencyStateTracker(origAddr);
         }
     }
 
@@ -497,15 +503,30 @@
 
     @Test
     @SmallTest
-    public void testNotifySmsSentToEmergencyStateTracker() throws Exception {
+    public void testNotifySmsSentToEmergencyStateTrackerOnDomainCs() throws Exception {
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, false);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("911"));
-        verify(mEmergencyStateTracker).endSms(eq("1"), eq(true));
+        verify(mEmergencyStateTracker)
+                .endSms(eq("1"), eq(true), eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsSentToEmergencyStateTrackerOnDomainPs() throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, true);
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("911"));
+        verify(mEmergencyStateTracker)
+                .endSms(eq("1"), eq(true), eq(NetworkRegistrationInfo.DOMAIN_PS));
     }
 
     @Test
@@ -514,11 +535,11 @@
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("1234", 1L);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("1234", 1L, true);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
-        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean());
+        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean(), anyInt());
     }
 
     @Test
@@ -526,22 +547,37 @@
     public void testNotifySmsSentToEmergencyStateTrackerWithoutEmergencyStateTracker()
             throws Exception {
         setUpDomainSelectionEnabled(true);
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, true);
 
         verify(mTelephonyManager, never()).isEmergencyNumber(anyString());
     }
 
     @Test
     @SmallTest
-    public void testNotifySmsSentFailedToEmergencyStateTracker() throws Exception {
+    public void testNotifySmsSentFailedToEmergencyStateTrackerOnDomainCs() throws Exception {
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("911", 1L);
+        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("911", 1L, false);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("911"));
-        verify(mEmergencyStateTracker).endSms(eq("1"), eq(false));
+        verify(mEmergencyStateTracker)
+                .endSms(eq("1"), eq(false), eq(NetworkRegistrationInfo.DOMAIN_CS));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsSentFailedToEmergencyStateTrackerOnDomainPs() throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("911", 1L, true);
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("911"));
+        verify(mEmergencyStateTracker)
+                .endSms(eq("1"), eq(false), eq(NetworkRegistrationInfo.DOMAIN_PS));
     }
 
     @Test
@@ -551,11 +587,38 @@
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("1234", 1L);
+        mSmsDispatchersController.testNotifySmsSentFailedToEmergencyStateTracker("1234", 1L, true);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
-        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean());
+        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsReceivedViaImsToEmergencyStateTracker() throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsReceivedViaImsToEmergencyStateTracker("911");
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("911"));
+        verify(mEmergencyStateTracker).onEmergencySmsReceived();
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySmsReceivedViaImsToEmergencyStateTrackerWithNonEmergencyNumber()
+            throws Exception {
+        setUpDomainSelectionEnabled(true);
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.testNotifySmsReceivedViaImsToEmergencyStateTracker("1234");
+        processAllMessages();
+
+        verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
+        verify(mEmergencyStateTracker, never()).onEmergencySmsReceived();
     }
 
     @Test
@@ -780,7 +843,7 @@
                 mSmsDispatchersController, mEmergencyStateTracker);
         when(mEmergencyStateTracker.startEmergencySms(any(Phone.class), anyString(), anyBoolean()))
                 .thenReturn(mEmergencySmsFuture);
-        doNothing().when(mEmergencyStateTracker).endSms(anyString(), anyBoolean());
+        doNothing().when(mEmergencyStateTracker).endSms(anyString(), anyBoolean(), anyInt());
         mEmergencySmsFuture.complete(result);
         when(mTelephonyManager.isEmergencyNumber(eq("911"))).thenReturn(true);
     }
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 9fa88e2..ac92b8f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
@@ -17,7 +17,10 @@
 
 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;
 
@@ -25,12 +28,18 @@
 
 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 {
@@ -39,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")
@@ -55,6 +66,9 @@
                 .setHplmns(HPLMNS)
                 .setCountryIso("us")
                 .setOnlyNonTerrestrialNetwork(true)
+                .setServiceCapabilities(SubscriptionManager.getServiceCapabilitiesSet(
+                    SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK))
+                .setTransferStatus(1)
                 .build();
     }
 
@@ -77,6 +91,11 @@
         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
@@ -101,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 ad66b55..6369825 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -46,7 +46,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 com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -58,6 +59,7 @@
 
 import java.lang.reflect.Field;
 import java.util.Map;
+
 @SmallTest
 public class TelephonyPermissionsTest {
 
@@ -168,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));
     }
@@ -210,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));
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index 5a5e11f..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;
 
@@ -115,6 +116,7 @@
     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;
@@ -160,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
@@ -187,7 +191,8 @@
             TelephonyCallback.CellInfoListener,
             TelephonyCallback.BarringInfoListener,
             TelephonyCallback.RegistrationFailedListener,
-            TelephonyCallback.DataActivityListener {
+            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);
@@ -275,6 +280,13 @@
             invocationCount.incrementAndGet();
             mDataActivity = direction;
         }
+
+        @Override
+        public void onSimultaneousCellularCallingSubscriptionsChanged(
+                @NonNull Set<Integer> simultaneousCallingSubscriptionIds) {
+            invocationCount.incrementAndGet();
+            mSimultaneousCallingSubscriptions = simultaneousCallingSubscriptionIds;
+        }
     }
 
     private void addTelephonyRegistryService() {
@@ -1531,4 +1543,23 @@
         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 9212567..673acbc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -115,6 +115,7 @@
 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;
@@ -124,6 +125,8 @@
 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;
@@ -252,6 +255,7 @@
     protected IntentBroadcaster mIntentBroadcaster;
     protected NitzStateMachine mNitzStateMachine;
     protected RadioConfig mMockRadioConfig;
+    protected RadioConfigProxy mMockRadioConfigProxy;
     protected LocaleTracker mLocaleTracker;
     protected RestrictedState mRestrictedState;
     protected PhoneConfigurationManager mPhoneConfigurationManager;
@@ -264,6 +268,7 @@
     protected CarrierPrivilegesTracker mCarrierPrivilegesTracker;
     protected VoiceCallSessionStats mVoiceCallSessionStats;
     protected PersistAtomsStorage mPersistAtomsStorage;
+    protected DefaultNetworkMonitor mDefaultNetworkMonitor;
     protected MetricsCollector mMetricsCollector;
     protected SmsStats mSmsStats;
     protected TelephonyAnalytics mTelephonyAnalytics;
@@ -281,8 +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;
@@ -417,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);
@@ -524,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);
@@ -536,6 +554,7 @@
         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);
@@ -553,8 +572,12 @@
         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);
+
+        doReturn(true).when(mFeatureFlags).minimalTelephonyCdmCheck();
 
         TelephonyManager.disableServiceHandleCaching();
         PropertyInvalidatedCache.disableForTestMode();
@@ -619,15 +642,15 @@
                         nullable(CommandsInterface.class), nullable(FeatureFlags.class));
         doReturn(mEmergencyNumberTracker).when(mTelephonyComponentFactory)
                 .makeEmergencyNumberTracker(nullable(Phone.class),
-                        nullable(CommandsInterface.class));
+                        nullable(CommandsInterface.class), any(FeatureFlags.class));
         doReturn(getTestEmergencyNumber()).when(mEmergencyNumberTracker)
                 .getEmergencyNumber(any());
         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)
@@ -658,7 +681,7 @@
                 .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)
@@ -668,9 +691,14 @@
                         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();
+                .makeIdentifierDisclosureNotifier(any(CellularNetworkSecuritySafetySource.class));
+        doReturn(mNullCipherNotifier)
+                .when(mTelephonyComponentFactory)
+                .makeNullCipherNotifier(any(CellularNetworkSecuritySafetySource.class));
 
         //mPhone
         doReturn(mContext).when(mPhone).getContext();
@@ -795,6 +823,12 @@
         doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
         doReturn(true).when(mTelephonyManager).isDataCapable();
 
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELECOM);
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING);
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_DATA);
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC);
+        mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING);
+
         doReturn(TelephonyManager.PHONE_TYPE_GSM).when(mTelephonyManager).getPhoneType();
         doReturn(mServiceState).when(mSST).getServiceState();
         doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
@@ -812,6 +846,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();
@@ -884,6 +919,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)
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/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/configupdate/TelephonyConfigUpdateInstallReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiverTest.java
new file mode 100644
index 0000000..629327d
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiverTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.configupdate;
+
+import static com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver.UPDATE_CONTENT_PATH;
+import static com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver.UPDATE_DIR;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.satellite.SatelliteConfigParser;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Base64;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class TelephonyConfigUpdateInstallReceiverTest extends TelephonyTest {
+
+    public static final String DOMAIN_SATELLITE = "satellite";
+    @Mock
+    private Executor mExecutor;
+    @Mock
+    private ConfigProviderAdaptor.Callback mCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd(TAG + " tearDown");
+        super.tearDown();
+    }
+
+    @Test
+    public void testTelephonyConfigUpdateInstallReceiver() {
+        TelephonyConfigUpdateInstallReceiver testReceiver =
+                new TelephonyConfigUpdateInstallReceiver();
+        assertEquals(UPDATE_DIR, testReceiver.getUpdateDir().toString());
+        assertEquals(new File(new File(UPDATE_DIR), UPDATE_CONTENT_PATH).toString(),
+                testReceiver.getUpdateContent().toString());
+    }
+
+    @Test
+    public void testGetInstance() {
+        TelephonyConfigUpdateInstallReceiver testReceiver1 =
+                TelephonyConfigUpdateInstallReceiver.getInstance();
+        TelephonyConfigUpdateInstallReceiver testReceiver2 =
+                TelephonyConfigUpdateInstallReceiver.getInstance();
+        assertSame(testReceiver1, testReceiver2);
+    }
+
+    @Test
+    public void testPostInstall() throws Exception {
+        // create spyTelephonyConfigUpdateInstallReceiver
+        TelephonyConfigUpdateInstallReceiver spyTelephonyConfigUpdateInstallReceiver =
+                spy(new TelephonyConfigUpdateInstallReceiver());
+
+        // mock BeforeParser
+        String mBase64StrForPBByteArray =
+                "CjYIBBIeCAESDgoGMzEwMTYwEAEQAhADEgoKBjMxMDIyMBADGhIKCjAxMjM0NTY3ODkSAlVTGAE=";
+        byte[] mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
+        doReturn(mBytesProtoBuffer).when(
+                spyTelephonyConfigUpdateInstallReceiver).getCurrentContent();
+        SatelliteConfigParser mMockSatelliteConfigParserBefore =
+                spy(new SatelliteConfigParser(mBytesProtoBuffer));
+        doReturn(mMockSatelliteConfigParserBefore).when(
+                spyTelephonyConfigUpdateInstallReceiver).getConfigParser(DOMAIN_SATELLITE);
+
+        // mock UpdatedParser
+        SatelliteConfigParser spySatelliteConfigParserAfter =
+                spy(new SatelliteConfigParser(mBytesProtoBuffer));
+        doReturn(5).when(spySatelliteConfigParserAfter).getVersion();
+        doReturn(spySatelliteConfigParserAfter).when(spyTelephonyConfigUpdateInstallReceiver)
+                .getNewConfigParser(any(), any());
+
+        replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance",
+                null, spyTelephonyConfigUpdateInstallReceiver);
+
+        assertSame(spyTelephonyConfigUpdateInstallReceiver,
+                TelephonyConfigUpdateInstallReceiver.getInstance());
+
+        ConcurrentHashMap<Executor, ConfigProviderAdaptor.Callback> spyCallbackHashMap = spy(
+                new ConcurrentHashMap<>());
+        spyCallbackHashMap.put(mExecutor, mCallback);
+        spyTelephonyConfigUpdateInstallReceiver.setCallbackMap(spyCallbackHashMap);
+        spyTelephonyConfigUpdateInstallReceiver.postInstall(mContext, new Intent());
+
+        verify(spyCallbackHashMap, atLeast(1)).keySet();
+    }
+
+
+    @Test
+    public void testGetConfig() throws Exception {
+        TelephonyConfigUpdateInstallReceiver spyTelephonyConfigUpdateInstallReceiver =
+                spy(new TelephonyConfigUpdateInstallReceiver());
+
+        doReturn(null).when(
+                spyTelephonyConfigUpdateInstallReceiver).getCurrentContent();
+
+        replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance",
+                null, spyTelephonyConfigUpdateInstallReceiver);
+        assertNull(TelephonyConfigUpdateInstallReceiver.getInstance().getConfigParser(
+                DOMAIN_SATELLITE));
+
+        String mBase64StrForPBByteArray =
+                "CjYIBBIeCAESDgoGMzEwMTYwEAEQAhADEgoKBjMxMDIyMBADGhIKCjAxMjM0NTY3ODkSAlVTGAE=";
+        byte[] mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
+        doReturn(mBytesProtoBuffer).when(
+                spyTelephonyConfigUpdateInstallReceiver).getCurrentContent();
+
+        replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance",
+                null, spyTelephonyConfigUpdateInstallReceiver);
+
+        assertNotNull(TelephonyConfigUpdateInstallReceiver.getInstance().getConfigParser(
+                DOMAIN_SATELLITE));
+    }
+
+    @Test
+    public void testRegisterUnRegisterCallback() {
+        TelephonyConfigUpdateInstallReceiver testReceiver =
+                TelephonyConfigUpdateInstallReceiver.getInstance();
+
+        ConfigProviderAdaptor.Callback testCallback = new ConfigProviderAdaptor.Callback() {
+            @Override
+            public void onChanged(@Nullable ConfigParser config) {
+                super.onChanged(config);
+            }
+        };
+        Executor executor = Executors.newSingleThreadExecutor();
+
+        testReceiver.registerCallback(executor, testCallback);
+        assertSame(testCallback, testReceiver.getCallbackMap().get(executor));
+
+        testReceiver.unregisterCallback(testCallback);
+        assertEquals(0, testReceiver.getCallbackMap().size());
+    }
+}
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 91ed03f..d1e5066 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
@@ -279,9 +279,9 @@
         processAllMessages();
 
         verify(mMockedCallback).onPreferredTransportChanged(
-                eq(NetworkCapabilities.NET_CAPABILITY_MMS));
+                eq(NetworkCapabilities.NET_CAPABILITY_MMS), eq(false));
         verify(mMockedCallback).onPreferredTransportChanged(
-                eq(NetworkCapabilities.NET_CAPABILITY_IMS));
+                eq(NetworkCapabilities.NET_CAPABILITY_IMS), eq(false));
         Mockito.clearInvocations(mMockedCallback);
         assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
                 NetworkCapabilities.NET_CAPABILITY_MMS)).isEqualTo(
@@ -295,7 +295,7 @@
         processAllMessages();
 
         verify(mMockedCallback).onPreferredTransportChanged(
-                eq(NetworkCapabilities.NET_CAPABILITY_XCAP));
+                eq(NetworkCapabilities.NET_CAPABILITY_XCAP), eq(false));
         Mockito.clearInvocations(mMockedCallback);
         assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
                 NetworkCapabilities.NET_CAPABILITY_XCAP)).isEqualTo(
@@ -305,11 +305,12 @@
                 ApnSetting.TYPE_XCAP | ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS,
                 new int[]{});
         verify(mMockedCallback).onPreferredTransportChanged(
-                eq(NetworkCapabilities.NET_CAPABILITY_IMS));
+                eq(NetworkCapabilities.NET_CAPABILITY_IMS), eq(false));
         verify(mMockedCallback).onPreferredTransportChanged(
-                eq(NetworkCapabilities.NET_CAPABILITY_MMS));
+                eq(NetworkCapabilities.NET_CAPABILITY_MMS), eq(false));
         verify(mMockedCallback).onPreferredTransportChanged(
-                eq(NetworkCapabilities.NET_CAPABILITY_XCAP));
+                eq(NetworkCapabilities.NET_CAPABILITY_XCAP), eq(false));
+        Mockito.clearInvocations(mMockedCallback);
         assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
                 NetworkCapabilities.NET_CAPABILITY_IMS)).isEqualTo(
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
@@ -348,4 +349,57 @@
                 mIntegerConsumer);
     }
 
+    @Test
+    public void testCallbackForReconnectQualifiedNetworkTypeWithFlagEnabled()  throws Exception {
+        when(mFeatureFlags.reconnectQualifiedNetwork()).thenReturn(true);
+
+
+        mAccessNetworksManager.registerCallback(mMockedCallback);
+
+        mQnsCallback.onReconnectQualifedNetworkType(ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS,
+                AccessNetworkType.IWLAN);
+        processAllMessages();
+
+        verify(mMockedCallback).onPreferredTransportChanged(
+                eq(NetworkCapabilities.NET_CAPABILITY_MMS), eq(true));
+        verify(mMockedCallback).onPreferredTransportChanged(
+                eq(NetworkCapabilities.NET_CAPABILITY_IMS), eq(true));
+        verify(mMockedCallback, never()).onPreferredTransportChanged(
+                eq(NetworkCapabilities.NET_CAPABILITY_XCAP), eq(true));
+        Mockito.clearInvocations(mMockedCallback);
+        assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
+                NetworkCapabilities.NET_CAPABILITY_MMS)).isEqualTo(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
+                NetworkCapabilities.NET_CAPABILITY_IMS)).isEqualTo(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
+                NetworkCapabilities.NET_CAPABILITY_XCAP)).isEqualTo(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+    }
+
+    @Test
+    public void testCallbackForReconnectQualifiedNetworkTypeWithFlagDisabled() throws Exception {
+        when(mFeatureFlags.reconnectQualifiedNetwork()).thenReturn(false);
+        mQnsCallback.onReconnectQualifedNetworkType(ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS,
+                AccessNetworkType.IWLAN);
+        processAllMessages();
+
+        verify(mMockedCallback, never()).onPreferredTransportChanged(
+                eq(NetworkCapabilities.NET_CAPABILITY_MMS), eq(true));
+        verify(mMockedCallback, never()).onPreferredTransportChanged(
+                eq(NetworkCapabilities.NET_CAPABILITY_IMS), eq(true));
+        verify(mMockedCallback, never()).onPreferredTransportChanged(
+                eq(NetworkCapabilities.NET_CAPABILITY_XCAP), eq(true));
+        Mockito.clearInvocations(mMockedCallback);
+        assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
+                NetworkCapabilities.NET_CAPABILITY_MMS)).isEqualTo(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
+                NetworkCapabilities.NET_CAPABILITY_IMS)).isEqualTo(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        assertThat(mAccessNetworksManager.getPreferredTransportByNetworkCapability(
+                NetworkCapabilities.NET_CAPABILITY_XCAP)).isEqualTo(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+    }
 }
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 378df4b..0c7342e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/ApnSettingTest.java
@@ -28,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;
@@ -42,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 {
 
@@ -206,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);
@@ -414,4 +420,33 @@
         // 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 6462d73..ddbe9c0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
@@ -20,10 +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;
@@ -32,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;
@@ -59,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 {
@@ -66,7 +74,7 @@
     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;
@@ -76,14 +84,17 @@
     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());
@@ -93,6 +104,7 @@
                 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();
@@ -105,6 +117,17 @@
 
         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();
@@ -133,8 +156,11 @@
 
         // Change data config
         doReturn(true).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired();
+        doReturn(true).when(mDataConfigManager).doesAutoDataSwitchAllowRoaming();
         doReturn(10000L).when(mDataConfigManager)
                 .getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        doReturn(120000L).when(mDataConfigManager)
+                .getAutoDataSwitchPerformanceStabilityTimeThreshold();
         doReturn(MAX_RETRY).when(mDataConfigManager).getAutoDataSwitchValidationMaxRetry();
         doReturn(SCORE_TOLERANCE).when(mDataConfigManager).getAutoDataSwitchScoreTolerance();
         doAnswer(invocation -> {
@@ -154,7 +180,13 @@
         mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(),
                 mPhoneSwitcher, mFeatureFlags, mMockedPhoneSwitcherCallback);
 
-        doReturn(true).when(mFeatureFlags).autoSwitchAllowRoaming();
+        replaceInstance(AutoDataSwitchController.class, "mAlarmManager",
+                mAutoDataSwitchControllerUT, mMockedAlarmManager);
+        mEventsToAlarmListener = getPrivateField(mAutoDataSwitchControllerUT,
+                "mEventsToAlarmListener", Map.class);
+
+        doReturn(true).when(mFeatureFlags).autoDataSwitchAllowRoaming();
+        doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
     }
 
     @After
@@ -246,6 +278,31 @@
     }
 
     @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();
@@ -318,6 +375,7 @@
 
     @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();
@@ -333,30 +391,39 @@
         // but primary become service, then don't switch.
         prepareIdealUsesNonDdsCondition();
         processAllFutureMessages();
-        clearInvocations(mMockedPhoneSwitcherCallback);
+        clearInvocations(mMockedPhoneSwitcherCallback, mMockedAlarmManager);
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
         signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_MODERATE);
         processAllFutureMessages();
-        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+        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);
+        clearInvocations(mMockedPhoneSwitcherCallback, mMockedAlarmManager);
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         displayInfoChanged(PHONE_1, mGoodTelephonyDisplayInfo);
         processAllFutureMessages();
-        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+        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);
+        clearInvocations(mMockedPhoneSwitcherCallback, mMockedAlarmManager);
         serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         signalStrengthChanged(PHONE_1, SignalStrength.SIGNAL_STRENGTH_GREAT);
         processAllFutureMessages();
-        verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedPhoneSwitcherCallback, atLeastOnce())
+                .onRequireCancelAnyPendingAutoSwitchValidation();
+        verify(mMockedAlarmManager, atLeastOnce()).cancel(mEventsToAlarmListener.get(
+                EVENT_STABILITY_CHECK_PASSED));
     }
 
     @Test
@@ -421,16 +488,18 @@
     @Test
     public void testOnNonDdsSwitchBackToPrimary_rat_signalStrength() {
         doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
+        prepareIdealUsesNonDdsCondition();
+        processAllFutureMessages();
         doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
 
-        prepareIdealUsesNonDdsCondition();
         // 4.1 Display info and signal strength on secondary phone became bad just as the default
-        // Expect no switch since both phone has the same score.
+        // 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, never()).onRequireValidation(anyInt(), anyBoolean());
+        verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
+                true/*needValidation*/);
 
         clearInvocations(mMockedPhoneSwitcherCallback);
         prepareIdealUsesNonDdsCondition();
@@ -466,7 +535,7 @@
     }
 
     @Test
-    public void testStabilityCheckOverride() {
+    public void testStabilityCheckOverride_basic() {
         // Starting stability check for switching to non-DDS
         prepareIdealUsesNonDdsCondition();
         processAllMessages();
@@ -477,9 +546,6 @@
         // 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);
-        signalStrengthChanged(PHONE_1, SignalStrength.SIGNAL_STRENGTH_GREAT);
-        displayInfoChanged(PHONE_2, mBadTelephonyDisplayInfo);
-        signalStrengthChanged(PHONE_2, SignalStrength.SIGNAL_STRENGTH_POOR);
         // process all messages include the delayed message
         processAllFutureMessages();
 
@@ -490,6 +556,25 @@
     }
 
     @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();
 
@@ -605,6 +690,7 @@
     @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())
@@ -677,6 +763,7 @@
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setRegistrationState(dataRegState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
                 .build());
 
         ss.setDataRoamingFromRegistration(dataRegState
@@ -687,9 +774,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 c8ab063..c0a9211 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataCallResponseTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataCallResponseTest.java
@@ -28,7 +28,8 @@
 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;
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 6d4c0c1..8ce0894 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataEvaluationTest.java
@@ -58,6 +58,14 @@
         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);
     }
 
 
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 a2c9724..9423551 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -648,8 +648,26 @@
     private void serviceStateChanged(@NetworkType int networkType,
             @RegistrationState int dataRegState, @RegistrationState int voiceRegState,
             @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri) {
+        boolean isEmergencyOnly = false;
+        if (dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_DENIED) {
+            isEmergencyOnly = true;
+        }
         ServiceState ss = createSS(networkType, networkType, dataRegState, voiceRegState,
-                iwlanRegState, dsri);
+                iwlanRegState, dsri, isEmergencyOnly);
+
+        doReturn(ss).when(mSST).getServiceState();
+        doReturn(ss).when(mPhone).getServiceState();
+
+        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
+        processAllMessages();
+    }
+
+    private void serviceStateChanged(@NetworkType int networkType,
+            @RegistrationState int dataRegState, @RegistrationState int voiceRegState,
+            @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri,
+            boolean isEmergencyOnly) {
+        ServiceState ss = createSS(networkType, networkType, dataRegState, voiceRegState,
+                iwlanRegState, dsri, isEmergencyOnly);
 
         doReturn(ss).when(mSST).getServiceState();
         doReturn(ss).when(mPhone).getServiceState();
@@ -661,7 +679,8 @@
     private ServiceState createSS(@NetworkType int dataNetworkType,
             @NetworkType int voiceNetworkType,
             @RegistrationState int dataRegState, @RegistrationState int voiceRegState,
-            @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri) {
+            @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri,
+            boolean isEmergencyOnly) {
         if (dsri == null) {
             dsri = new DataSpecificRegistrationInfo.Builder(8)
                     .setNrAvailable(true)
@@ -682,6 +701,7 @@
                 .setDataSpecificInfo(dsri)
                 .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
                 .setAvailableServices(mCarrierSupportedSatelliteServices)
+                .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
         ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
@@ -691,6 +711,7 @@
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
                 .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
                 .setAvailableServices(mCarrierSupportedSatelliteServices)
+                .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
         ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
@@ -698,6 +719,7 @@
                 .setAccessNetworkTechnology(voiceNetworkType)
                 .setRegistrationState(voiceRegState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
         ss.setDataRoamingFromRegistration(dataRegState
@@ -731,7 +753,14 @@
     private void updateTransport(@NetCapability int capability, @TransportType int transport) {
         doReturn(transport).when(mAccessNetworksManager)
                 .getPreferredTransportByNetworkCapability(capability);
-        mAccessNetworksManagerCallback.onPreferredTransportChanged(capability);
+        mAccessNetworksManagerCallback.onPreferredTransportChanged(capability, false);
+        processAllMessages();
+    }
+
+    private void reconnectTransport(@NetCapability int capability, @TransportType int transport) {
+        doReturn(transport).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(capability);
+        mAccessNetworksManagerCallback.onPreferredTransportChanged(capability, true);
         processAllMessages();
     }
 
@@ -775,9 +804,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(
@@ -800,6 +830,12 @@
         mCarrierConfig.putIntArray(CarrierConfigManager
                         .KEY_CAPABILITIES_EXEMPT_FROM_SINGLE_DC_CHECK_INT_ARRAY,
                 new int[]{NetworkCapabilities.NET_CAPABILITY_IMS});
+        mCarrierConfig.putBooleanArray(
+                CarrierConfigManager.KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY,
+                new boolean[] {false, false, true, false, false}
+        );
+        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});
@@ -810,6 +846,8 @@
         mContextFixture.putResource(com.android.internal.R.string.config_bandwidthEstimateSource,
                 "bandwidth_estimator");
 
+        mContextFixture.putBooleanResource(com.android.internal.R.bool
+                .config_honor_data_retry_timer_for_emergency_network, true);
         mContextFixture.putIntResource(com.android.internal.R.integer
                         .config_delay_for_ims_dereg_millis, 3000);
         mContextFixture.putBooleanResource(com.android.internal.R.bool
@@ -834,7 +872,7 @@
         mMockSubInfo = Mockito.mock(SubscriptionInfo.class);
         mFeatureFlags = Mockito.mock(FeatureFlags.class);
         when(mTelephonyComponentFactory.makeDataSettingsManager(any(Phone.class),
-                any(DataNetworkController.class), any(Looper.class),
+                any(DataNetworkController.class), any(FeatureFlags.class), any(Looper.class),
                 any(DataSettingsManager.DataSettingsManagerCallback.class))).thenCallRealMethod();
         doReturn(mMockedImsMmTelManager).when(mMockedImsManager).getImsMmTelManager(anyInt());
         doReturn(mMockedImsRcsManager).when(mMockedImsManager).getImsRcsManager(anyInt());
@@ -1819,7 +1857,8 @@
         boolean isDataEnabled = mDataNetworkControllerUT.getDataSettingsManager().isDataEnabled();
         doReturn(mDataNetworkControllerUT.getDataSettingsManager())
                 .when(mPhone).getDataSettingsManager();
-        MultiSimSettingController controller = Mockito.spy(new MultiSimSettingController(mContext));
+        MultiSimSettingController controller = Mockito.spy(new MultiSimSettingController(mContext,
+                mFeatureFlags));
         doReturn(true).when(controller).isCarrierConfigLoadedForAllSub();
         replaceInstance(MultiSimSettingController.class, "sInstance", null, controller);
 
@@ -2117,6 +2156,53 @@
     }
 
     @Test
+    public void testEmergencyRequestWithThrottling() throws Exception {
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* data */
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* voice */
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* iwlan */
+                null, false);
+        mDataNetworkControllerUT.getDataRetryManager()
+                .registerCallback(mMockedDataRetryManagerCallback);
+
+        setFailedSetupDataResponse(mMockedWwanDataServiceManager, DataFailCause.PROTOCOL_ERRORS,
+                10000, false);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_EIMS));
+        processAllMessages();
+
+        // There should be only one attempt, and no retry should happen because the second one
+        // was throttled.
+        verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+                any(), any(), anyBoolean(), any(Message.class));
+
+        ArgumentCaptor<List<ThrottleStatus>> throttleStatusCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mMockedDataRetryManagerCallback)
+                .onThrottleStatusChanged(throttleStatusCaptor.capture());
+        assertThat(throttleStatusCaptor.getValue()).hasSize(1);
+        ThrottleStatus throttleStatus = throttleStatusCaptor.getValue().get(0);
+        assertThat(throttleStatus.getApnType()).isEqualTo(ApnSetting.TYPE_EMERGENCY);
+        assertThat(throttleStatus.getRetryType())
+                .isEqualTo(ThrottleStatus.RETRY_TYPE_NEW_CONNECTION);
+        assertThat(throttleStatus.getTransportType())
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        Mockito.reset(mMockedWwanDataServiceManager);
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_DENIED, /* data */
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* voice */
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, /* iwlan */
+                null, true);
+
+        // No retry should happen because the second one was throttled.
+        verify(mMockedWwanDataServiceManager, never()).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+                any(), any(), anyBoolean(), any(Message.class));
+    }
+
+    @Test
     public void testHandoverRuleFromString() {
         HandoverRule handoverRule = new HandoverRule("source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
                 + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed");
@@ -2880,7 +2966,7 @@
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
                 .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
         mAccessNetworksManagerCallback.onPreferredTransportChanged(
-                NetworkCapabilities.NET_CAPABILITY_IMS);
+                NetworkCapabilities.NET_CAPABILITY_IMS, false);
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_UMTS,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         processAllMessages();
@@ -3947,6 +4033,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);
@@ -4629,7 +4759,7 @@
                 TelephonyManager.NETWORK_TYPE_1xRTT /* voice RAT */,
                 NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING ,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
-                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, null);
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, null, true);
         doReturn(ss).when(mSST).getServiceState();
         mDataNetworkControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED).sendToTarget();
         mDataNetworkControllerUT.removeNetworkRequest(request);
@@ -4790,6 +4920,9 @@
     @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());
@@ -4817,6 +4950,65 @@
     }
 
     @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)
@@ -4842,6 +5034,9 @@
     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());
@@ -4930,4 +5125,73 @@
         assertThat(waitForIntegerConsumerResponse(1 /*numOfEvents*/)).isTrue();
         assertThat(mIntegerConsumerResult).isEqualTo(DataServiceCallback.RESULT_ERROR_INVALID_ARG);
     }
+
+    @Test
+    public void testForceReconnectToPreferredTransportType() throws Exception {
+        testSetupImsDataNetwork();
+        reconnectTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // Verify IMS network was torn down on source first.
+        verify(mMockedWwanDataServiceManager).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
+
+        // Verify that IWLAN is brought up again on IWLAN.
+        verify(mMockedWlanDataServiceManager).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), any(), anyBoolean(),
+                any(Message.class));
+
+        DataNetwork dataNetwork = getDataNetworks().get(0);
+        assertThat(dataNetwork.getTransport()).isEqualTo(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+    }
+
+    @Test
+    public void testForceReconnectIgnored() throws Exception {
+        mCarrierConfig.putStringArray(
+                CarrierConfigManager.KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY,
+                new String[]{
+                        "source=EUTRAN|NGRAN|IWLAN|UNKNOWN, target=EUTRAN|NGRAN|IWLAN, "
+                                + "type=allowed, capabilities=IMS"
+                });
+        carrierConfigChanged();
+
+        testSetupImsDataNetwork();
+        reconnectTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        // request reconnection to current transport type, tear down should not happen.
+        verify(mMockedWwanDataServiceManager, never()).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
+
+        Mockito.reset(mMockedWlanDataServiceManager);
+
+        // Trigger Handover to IWLAN.
+        updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // Capture the message for setup data call response. We want to delay it.
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockedWlanDataServiceManager).setupDataCall(anyInt(), any(DataProfile.class),
+                anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(), any(), any(), anyBoolean(),
+                messageCaptor.capture());
+
+        // Force reconnect to preferred transport type while handover is in progress.
+        reconnectTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        // Finally handover is completed.
+        Message msg = messageCaptor.getValue();
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(DataFailCause.NONE)
+                .build();
+        msg.getData().putParcelable("data_call_response", response);
+        msg.arg1 = DataServiceCallback.RESULT_SUCCESS;
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mMockedWwanDataServiceManager, never()).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
+    }
 }
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 5dd83bf..ebfc41a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -68,6 +69,7 @@
 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;
@@ -76,6 +78,7 @@
 
 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;
@@ -83,6 +86,7 @@
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
+import com.android.internal.telephony.test.SimulatedCommands;
 
 import org.junit.After;
 import org.junit.Before;
@@ -123,7 +127,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)
@@ -134,6 +138,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")
@@ -157,6 +173,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))
@@ -930,7 +951,6 @@
                 eq(DataService.REQUEST_REASON_SHUTDOWN), any(Message.class));
     }
 
-
     @Test
     public void testCreateDataNetworkOnIwlan() throws Exception {
         doReturn(mIwlanNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
@@ -1152,6 +1172,59 @@
     }
 
     @Test
+    public void testNetworkRequestDetachedBeforePduSessionIdAllocated() throws Exception {
+        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();
+        TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                .build(), mPhone);
+        networkRequestList.add(networkRequest);
+
+        SimulatedCommands simulatedCommands2 = mock(SimulatedCommands.class);
+        mPhone.mCi = simulatedCommands2;
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mImsDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, DataAllowedReason.NORMAL,
+                mDataNetworkCallback);
+        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
+                mDataNetworkUT, mDataCallSessionStats);
+        processAllMessages();
+        // Capture the message for allocatePduSessionId response. We want to delay it.
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(simulatedCommands2).allocatePduSessionId(messageCaptor.capture());
+        // Detach attached network request.
+        mDataNetworkUT.detachNetworkRequest(networkRequest, false);
+        processAllMessages();
+
+        // Pass response msg for the PDU session ID allocation.
+        Message msg = messageCaptor.getValue();
+        AsyncResult.forMessage(msg, 1, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Check setupDataCall was not called.
+        verify(mMockedWlanDataServiceManager, never()).setupDataCall(eq(AccessNetworkType.IWLAN),
+                eq(mImsDataProfile), eq(false), eq(false),
+                eq(DataService.REQUEST_REASON_NORMAL), nullable(LinkProperties.class),
+                eq(1), nullable(NetworkSliceInfo.class),
+                any(TrafficDescriptor.class), eq(true), any(Message.class));
+
+        // Check state changed to DISCONNECTED from CONNECTING
+        ArgumentCaptor<PreciseDataConnectionState> pdcsCaptor =
+                ArgumentCaptor.forClass(PreciseDataConnectionState.class);
+        verify(mPhone, times(2)).notifyDataConnection(pdcsCaptor.capture());
+        List<PreciseDataConnectionState> pdcsList = pdcsCaptor.getAllValues();
+        assertThat(pdcsList.get(0).getState()).isEqualTo(TelephonyManager.DATA_CONNECTING);
+        assertThat(pdcsList.get(1).getState()).isEqualTo(TelephonyManager.DATA_DISCONNECTED);
+    }
+
+    @Test
     public void testAdminAndOwnerUids() throws Exception {
         doReturn(ADMIN_UID2).when(mCarrierPrivilegesTracker).getCarrierServicePackageUid();
         setupDataNetwork();
@@ -2028,13 +2101,13 @@
         // First Request
         mDataNetworkUT.requestNetworkValidation(mIntegerConsumer);
         processAllMessages();
-        verify(mMockedWlanDataServiceManager).requestValidation(eq(123 /*cid*/),
+        verify(mMockedWlanDataServiceManager).requestNetworkValidation(eq(123 /*cid*/),
                 any(Message.class));
 
         // Duplicate Request
         mDataNetworkUT.requestNetworkValidation(mIntegerConsumer);
         processAllMessages();
-        verify(mMockedWlanDataServiceManager).requestValidation(eq(123 /*cid*/),
+        verify(mMockedWlanDataServiceManager).requestNetworkValidation(eq(123 /*cid*/),
                 any(Message.class));
         assertThat(waitForIntegerConsumerResponse(1 /*numOfEvents*/)).isTrue();
         assertThat(mIntegerConsumerResult).isEqualTo(DataServiceCallback.RESULT_ERROR_BUSY);
@@ -2107,7 +2180,7 @@
         // Request Network Validation
         mDataNetworkUT.requestNetworkValidation(mIntegerConsumer);
         processAllMessages();
-        verify(mMockedWlanDataServiceManager).requestValidation(eq(123 /*cid*/),
+        verify(mMockedWlanDataServiceManager).requestNetworkValidation(eq(123 /*cid*/),
                 any(Message.class));
 
         // data state updated with network validation status
@@ -2203,4 +2276,83 @@
                 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, false);
+        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, false);
+        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 e556cb8..44d207d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
@@ -139,7 +139,8 @@
                 Telephony.Carriers.SKIP_464XLAT,
                 Telephony.Carriers.ALWAYS_ON,
                 Telephony.Carriers.INFRASTRUCTURE_BITMASK,
-                Telephony.Carriers.ESIM_BOOTSTRAP_PROVISIONING
+                Telephony.Carriers.ESIM_BOOTSTRAP_PROVISIONING,
+                Telephony.Carriers.EDITED_STATUS
         };
 
         private int mPreferredApnSet = 0;
@@ -180,7 +181,8 @@
                         -1,                     // skip_464xlat
                         0,                      // always_on
                         1,                      // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 // default internet data profile for RAT CDMA, to test update preferred data profile
                 new Object[]{
@@ -216,8 +218,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         2,                      // id
@@ -252,8 +255,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         3,                      // id
@@ -288,8 +292,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         4,                      // id
@@ -325,8 +330,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 // This APN entry is created to test de-duping.
                 new Object[]{
@@ -363,8 +369,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         6,                      // id
@@ -400,8 +407,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         7,                      // id
@@ -437,8 +445,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         8,                      // id
@@ -474,8 +483,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        0                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         9,                      // id
@@ -512,10 +522,11 @@
                         -1,                     // skip_464xlat
                         0,                      // always_on
                         2,                      // INFRASTRUCTURE_SATELLITE
-                        0                      // esim_bootstrap_provisioning
+                        0,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
-                        10,                      // id
+                        10,                     // id
                         PLMN,                   // numeric
                         ESIM_BOOTSTRAP_PROVISIONING_APN, // name
                         ESIM_BOOTSTRAP_PROVISIONING_APN, // apn
@@ -527,7 +538,7 @@
                         "",                     // user
                         "",                     // password
                         -1,                     // authtype
-                        "default,supl",          // types
+                        "default,supl",         // types
                         "IPV4V6",               // protocol
                         "IPV4V6",               // roaming_protocol
                         1,                      // carrier_enabled
@@ -548,8 +559,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_CELLULAR
-                        1                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_CELLULAR
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         11,                      // id
@@ -585,8 +597,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        1,                       // INFRASTRUCTURE_SATELLITE
-                        1                       // esim_bootstrap_provisioning
+                        1,                      // INFRASTRUCTURE_SATELLITE
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
                         12,                     // id
@@ -622,14 +635,15 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        2,                       // INFRASTRUCTURE_SATELLITE
-                        1                       // esim_bootstrap_provisioning
+                        2,                      // INFRASTRUCTURE_SATELLITE
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 },
                 new Object[]{
-                        13,                      // id
+                        13,                     // id
                         PLMN,                   // numeric
-                        RCS_APN1,                // name
-                        RCS_APN1,                // apn
+                        RCS_APN1,               // name
+                        RCS_APN1,               // apn
                         "",                     // proxy
                         "",                     // port
                         "",                     // mmsc
@@ -659,8 +673,9 @@
                         -1,                     // carrier_id
                         -1,                     // skip_464xlat
                         0,                      // always_on
-                        2,                       // INFRASTRUCTURE_SATELLITE
-                        1                       // esim_bootstrap_provisioning
+                        2,                      // INFRASTRUCTURE_SATELLITE
+                        1,                      // esim_bootstrap_provisioning
+                        0                       // UNEDITED
                 }
         );
 
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 2ceca0e..30f49ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataServiceManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataServiceManagerTest.java
@@ -269,18 +269,18 @@
     }
 
     @Test
-    public void testRequestValidation_ServiceNotBound() throws Exception {
+    public void testRequestNetworkValidation_ServiceNotBound() throws Exception {
         createDataServiceManager(false);
         Message message = mHandler.obtainMessage(1234);
-        mDataServiceManagerUT.requestValidation(123, message);
+        mDataServiceManagerUT.requestNetworkValidation(123, message);
         waitAndVerifyResult(message, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
     }
 
     @Test
-    public void testRequestValidation_ServiceBound() throws Exception {
+    public void testRequestNetworkValidation_ServiceBound() throws Exception {
         createDataServiceManager(true);
         Message message = mHandler.obtainMessage(1234);
-        mDataServiceManagerUT.requestValidation(123, message);
+        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 fc1bf0d..3f18a3a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
@@ -77,7 +77,7 @@
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
 
         mDataSettingsManagerUT = new DataSettingsManager(mPhone, mDataNetworkController,
-                Looper.myLooper(), mMockedDataSettingsManagerCallback);
+                mFeatureFlags, Looper.myLooper(), mMockedDataSettingsManagerCallback);
         logd("DataSettingsManagerTest -Setup!");
     }
 
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 e1e238e..9f8b8ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
@@ -43,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;
@@ -57,6 +58,8 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class DataStallRecoveryManagerTest extends TelephonyTest {
+    private static final String KEY_IS_DSRS_DIAGNOSTICS_ENABLED =
+            "is_dsrs_diagnostics_enabled";
     private FakeContentResolver mFakeContentResolver;
 
     // Mocked classes
@@ -88,6 +91,7 @@
         Field field = DataStallRecoveryManager.class.getDeclaredField("mPredictWaitingMillis");
         field.setAccessible(true);
 
+        doReturn(true).when(mFeatureFlags).dsrsDiagnosticsEnabled();
         mFakeContentResolver = new FakeContentResolver();
         doReturn(mFakeContentResolver).when(mContext).getContentResolver();
         // Set the global settings for action enabled state and duration to
@@ -107,7 +111,7 @@
         doReturn(dataStallRecoveryStepsArray)
                 .when(mDataConfigManager)
                 .getDataStallRecoveryShouldSkipArray();
-        doReturn(true).when(mDataNetworkController).isInternetDataAllowed();
+        doReturn(true).when(mDataNetworkController).isInternetDataAllowed(true);
 
         doAnswer(invocation -> {
             ((Runnable) invocation.getArguments()[0]).run();
@@ -119,6 +123,7 @@
                         mPhone,
                         mDataNetworkController,
                         mMockedWwanDataServiceManager,
+                        mFeatureFlags,
                         mTestableLooper.getLooper(),
                         mDataStallRecoveryManagerCallback);
 
@@ -145,6 +150,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);
@@ -257,7 +273,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
@@ -347,7 +364,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);
@@ -416,6 +433,7 @@
     @Test
     public void testSendDSRMData() throws Exception {
         ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        boolean isDsrsDiagnosticsEnabled = mFeatureFlags.dsrsDiagnosticsEnabled();
 
         logd("Set phone status to normal status.");
         sendOnInternetDataNetworkCallback(true);
@@ -447,8 +465,13 @@
             logd(bundle.toString());
             int size = bundle.size();
             logd("bundle size is " + size);
-            // Check if bundle size is 19
-            assertThat(size).isEqualTo(19);
+            if (isDsrsDiagnosticsEnabled) {
+                // Check if bundle size is 27
+                assertThat(size).isEqualTo(27);
+            } else {
+                // Check if bundle size is 19
+                assertThat(size).isEqualTo(19);
+            }
         }
     }
 
@@ -510,4 +533,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 719862a..e011a60 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
@@ -68,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;
@@ -1935,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() {
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/domainselection/DomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
index 1734244..47f8ce2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
@@ -24,29 +24,40 @@
 
 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;
 
@@ -56,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
@@ -78,102 +89,94 @@
         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);
 
-        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
 
         assertNotNull(transportCallback);
 
-        replaceInstance(DomainSelectionConnection.class, "mLooper",
-                mDsc, mTestableLooper.getLooper());
-        Consumer<WwanSelectorCallback> consumer = Mockito.mock(Consumer.class);
-        transportCallback.onWwanSelected(consumer);
-        processAllMessages();
+        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);
@@ -192,103 +195,307 @@
 
         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, null, null, null, null);
+                false, CallFailCause.ERROR_UNSPECIFIED, TELECOM_CALL_ID1, null, null, null);
 
         CompletableFuture<Integer> future = mDsc.reselectDomain(attr);
 
         assertNotNull(future);
         assertFalse(future.isDone());
 
-        verify(domainSelector).reselectDomain(any());
+        verify(domainSelector).reselectDomain(eq(attr));
     }
 
     @Test
     @SmallTest
     public void testDomainSelectorFinishSelection() 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);
+        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();
@@ -299,7 +506,7 @@
     @Test
     @SmallTest
     public void testQualifiedNetworkTypesChanged() throws Exception {
-        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
                 mDomainSelectionController);
 
         List<QualifiedNetworks> networksList = new ArrayList<>();
@@ -321,10 +528,265 @@
                 .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);
+
+        assertNotNull(future);
+        assertFalse(future.isDone());
+
+        verify(domainSelector).reselectDomain(eq(attr));
+        verify(mDomainSelectionController, times(1)).selectDomain(any(), eq(transportCallback));
+    }
+
+    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)
@@ -333,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 eaf11a4..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();
@@ -89,7 +105,7 @@
             DomainSelectionResolver.getInstance();
         });
 
-        DomainSelectionResolver.make(mContext, true);
+        DomainSelectionResolver.make(mContext, COMPONENT_NAME_STRING);
         DomainSelectionResolver resolver = DomainSelectionResolver.getInstance();
 
         assertNotNull(resolver);
@@ -97,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());
     }
@@ -106,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());
     }
@@ -114,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());
     }
@@ -122,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);
@@ -132,29 +148,36 @@
     @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 testGetDomainSelectionConnectionWhenImsPhoneNull() throws Exception {
-        setUpResolver(true, RADIO_HAL_VERSION_2_1);
-        mDsResolver.initialize(mDsService);
-        when(mPhone.getImsPhone()).thenReturn(null);
+        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 testGetDomainSelectionConnectionWhenImsNotAvailable() 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));
+
         when(mPhone.isImsAvailable()).thenReturn(false);
         when(mPhone.getImsPhone()).thenReturn(mImsPhone);
-
         assertNull(mDsResolver.getDomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, false));
     }
 
@@ -162,12 +185,13 @@
     @SmallTest
     public void testGetDomainSelectionConnectionWhenImsNotAvailableForEmergencyCall()
             throws Exception {
-        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
         setUpController();
-        mDsResolver.initialize(mDsService);
+        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));
     }
@@ -175,18 +199,81 @@
     @Test
     @SmallTest
     public void testGetDomainSelectionConnection() throws Exception {
-        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+        setUpResolver(COMPONENT_NAME_STRING, RADIO_HAL_VERSION_2_1);
         setUpController();
-        mDsResolver.initialize(mDsService);
+        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);
     }
 
@@ -194,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 d0b2fce..4a433c2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
@@ -42,15 +42,19 @@
 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.telephony.PreciseDisconnectCause;
 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 com.android.internal.telephony.CallFailCause;
+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;
@@ -77,7 +81,7 @@
     private DomainSelectionConnection.DomainSelectionConnectionCallback mConnectionCallback;
     private EmergencyCallDomainSelectionConnection mEcDsc;
     private AccessNetworksManager mAnm;
-    private TransportSelectorCallback mTransportCallback;
+    private ITransportSelectorCallback mTransportCallback;
     private EmergencyStateTracker mEmergencyStateTracker;
 
     @Before
@@ -85,6 +89,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);
@@ -92,6 +97,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();
     }
 
@@ -108,7 +116,7 @@
         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, "", "", "");
@@ -116,7 +124,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);
@@ -124,7 +132,8 @@
         assertNotNull(future);
         assertFalse(future.isDone());
 
-        verify(mDomainSelectionController).selectDomain(any(), any());
+        verify(mDomainSelectionController).selectDomain(any(),
+                any(ITransportSelectorCallback.class));
 
         mTransportCallback.onWlanSelected(true);
 
@@ -156,7 +165,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, "", "", "");
@@ -164,7 +173,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);
@@ -172,13 +181,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);
@@ -194,7 +203,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, "", "", "");
@@ -202,7 +211,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);
@@ -210,13 +219,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);
@@ -228,7 +237,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, "", "", "");
@@ -236,7 +245,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);
@@ -250,4 +259,51 @@
         mEcDsc.cancelSelection();
         verify(mAnm).unregisterForQualifiedNetworksChanged(any());
     }
+
+    @Test
+    @SmallTest
+    public void testGetSelectionAttributesEmergencyTempOrPermFailure() {
+        DomainSelectionService.SelectionAttributes attr =
+                EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", false,
+                        CallFailCause.IMS_EMERGENCY_TEMP_FAILURE, null, null);
+
+        assertEquals(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE, attr.getCsDisconnectCause());
+
+        attr = EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", false,
+                        CallFailCause.IMS_EMERGENCY_PERM_FAILURE, null, null);
+
+        assertEquals(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE, attr.getCsDisconnectCause());
+
+        attr = EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", false,
+                        CallFailCause.EMERGENCY_TEMP_FAILURE, null, null);
+
+        assertEquals(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE, attr.getCsDisconnectCause());
+
+        attr = EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", false,
+                        CallFailCause.EMERGENCY_PERM_FAILURE, null, null);
+
+        assertEquals(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE, attr.getCsDisconnectCause());
+    }
+
+    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 3b6d0c3..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,6 +25,7 @@
 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;
@@ -36,13 +37,14 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.DomainSelectionService;
-import android.telephony.DomainSelector;
 import android.telephony.NetworkRegistrationInfo;
 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 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;
@@ -67,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;
@@ -85,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);
@@ -441,7 +444,7 @@
 
         assertFalse(future.isDone());
         verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class));
-        verify(mDomainSelector).cancelSelection();
+        verify(mDomainSelector).finishSelection();
     }
 
     @Test
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/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/EmergencyNumberTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
index c47eb3b..10dbfea 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
@@ -51,7 +51,6 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyTest;
 
 import com.google.i18n.phonenumbers.ShortNumberInfo;
@@ -61,6 +60,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 
 
 import java.io.BufferedInputStream;
@@ -139,7 +139,7 @@
         mShortNumberInfo = mock(ShortNumberInfo.class);
         mCarrierConfigManagerMock = mock(CarrierConfigManager.class);
 
-        mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+        mContext = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         mMockContext = mock(Context.class);
         mResources = mock(Resources.class);
 
@@ -151,9 +151,14 @@
         doReturn(1).when(mPhone2).getPhoneId();
         doReturn(SUB_ID_PHONE_2).when(mPhone2).getSubId();
 
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mPackageManager).when(mMockContext).getPackageManager();
+
         initializeEmergencyNumberListTestSamples();
-        mEmergencyNumberTrackerMock = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
-        mEmergencyNumberTrackerMock2 = new EmergencyNumberTracker(mPhone2, mSimulatedCommands);
+        mEmergencyNumberTrackerMock = new EmergencyNumberTracker(mPhone, mSimulatedCommands,
+                mFeatureFlags);
+        mEmergencyNumberTrackerMock2 = new EmergencyNumberTracker(mPhone2, mSimulatedCommands,
+                mFeatureFlags);
         doReturn(mEmergencyNumberTrackerMock2).when(mPhone2).getEmergencyNumberTracker();
         mEmergencyNumberTrackerMock.DBG = true;
 
@@ -171,10 +176,13 @@
     public void tearDown() throws Exception {
         // Set back to single sim mode
         setSinglePhone();
-        Path target = Paths.get(mLocalDownloadDirectory.getPath(), EMERGENCY_NUMBER_DB_OTA_FILE);
-        Files.deleteIfExists(target);
-        mLocalDownloadDirectory.delete();
-        mLocalDownloadDirectory = null;
+        if (mLocalDownloadDirectory != null) {
+            Path target = Paths.get(mLocalDownloadDirectory.getPath(),
+                    EMERGENCY_NUMBER_DB_OTA_FILE);
+            Files.deleteIfExists(target);
+            mLocalDownloadDirectory.delete();
+            mLocalDownloadDirectory = null;
+        }
         mEmergencyNumberTrackerMock = null;
         mEmergencyNumberTrackerMock2 = null;
         mEmergencyNumberListTestSample.clear();
@@ -361,12 +369,12 @@
     @Test
     public void testRegistrationForCountryChangeIntent() throws Exception {
         EmergencyNumberTracker localEmergencyNumberTracker;
-        Context spyContext = spy(mContext);
-        doReturn(spyContext).when(mPhone).getContext();
+        Mockito.clearInvocations(mContext);
         ArgumentCaptor<IntentFilter> intentCaptor = ArgumentCaptor.forClass(IntentFilter.class);
 
-        localEmergencyNumberTracker = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
-        verify(spyContext, times(1)).registerReceiver(any(), intentCaptor.capture());
+        localEmergencyNumberTracker = new EmergencyNumberTracker(mPhone, mSimulatedCommands,
+                mFeatureFlags);
+        verify(mContext, times(1)).registerReceiver(any(), intentCaptor.capture());
         IntentFilter ifilter = intentCaptor.getValue();
         assertTrue(ifilter.hasAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED));
     }
@@ -543,7 +551,7 @@
                 com.android.internal.R.bool.ignore_emergency_number_routing_from_db);
 
         EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker(
-                mPhone, mSimulatedCommands);
+                mPhone, mSimulatedCommands, mFeatureFlags);
         emergencyNumberTrackerMock.sendMessage(
                 emergencyNumberTrackerMock.obtainMessage(
                         1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
@@ -616,7 +624,7 @@
                 com.android.internal.R.bool.ignore_emergency_number_routing_from_db);
 
         EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker(
-                mPhone, mSimulatedCommands);
+                mPhone, mSimulatedCommands, mFeatureFlags);
         emergencyNumberTrackerMock.sendMessage(
                 emergencyNumberTrackerMock.obtainMessage(
                         1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
@@ -720,7 +728,7 @@
                 com.android.internal.R.bool.ignore_emergency_number_routing_from_db);
 
         EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker(
-                mPhone, mSimulatedCommands);
+                mPhone, mSimulatedCommands, mFeatureFlags);
         emergencyNumberTrackerMock.sendMessage(
                 emergencyNumberTrackerMock.obtainMessage(
                         1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
@@ -843,7 +851,7 @@
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
         EmergencyNumberTracker localEmergencyNumberTracker =
-                new EmergencyNumberTracker(mPhone, mSimulatedCommands);
+                new EmergencyNumberTracker(mPhone, mSimulatedCommands, mFeatureFlags);
         verify(mCarrierConfigManagerMock)
                 .registerCarrierConfigChangeListener(any(), listenerArgumentCaptor.capture());
         CarrierConfigManager.CarrierConfigChangeListener carrierConfigChangeListener =
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 fff1b68..3eb7659 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -17,12 +17,15 @@
 package com.android.internal.telephony.emergency;
 
 import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+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.REGISTRATION_STATE_HOME;
 
 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,17 +55,19 @@
 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;
@@ -89,12 +94,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 int TEST_ECM_EXIT_TIMEOUT_MS = 500;
+    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;
@@ -102,6 +105,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 {
@@ -150,22 +155,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
@@ -192,13 +268,13 @@
                 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(0));
+                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);
@@ -227,8 +303,8 @@
                 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, TEST_CALL_ID,
-                false);
+        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone,
+                mTestConnection1, false);
 
         // startEmergencyCall should trigger radio on
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
@@ -269,7 +345,7 @@
         when(mSatelliteController.isSatelliteEnabled()).thenReturn(true);
 
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // startEmergencyCall should trigger satellite modem off
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
@@ -299,7 +375,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(),
@@ -324,7 +400,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());
@@ -346,7 +422,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());
@@ -373,7 +449,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 */,
@@ -401,7 +477,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 */,
@@ -410,7 +486,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
@@ -424,7 +500,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);
@@ -434,7 +510,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));
     }
 
@@ -454,13 +530,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());
@@ -481,15 +557,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());
@@ -511,15 +587,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());
@@ -543,15 +619,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());
@@ -571,7 +647,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);
@@ -580,6 +656,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.
      */
@@ -594,15 +691,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
@@ -629,13 +726,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());
     }
@@ -656,15 +753,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());
 
@@ -676,216 +773,6 @@
     }
 
     /**
-     * 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,
-                TEST_CALL_ID, false);
-        // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
-        emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
-        // Set ecm as supported
-        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
-
-        processAllMessages();
-
-        emergencyStateTracker.endCall(TEST_CALL_ID);
-
-        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,
-                TEST_CALL_ID, 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, TEST_CALL_ID);
-        emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
-        // Set ecm as supported
-        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
-
-        processAllMessages();
-
-        emergencyStateTracker.endCall(TEST_CALL_ID);
-
-        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, TEST_CALL_ID, 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,
-                TEST_CALL_ID, 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, TEST_CALL_ID);
-        emergencyStateTracker.onEmergencyCallDomainUpdated(
-                PhoneConstants.PHONE_TYPE_IMS, TEST_CALL_ID);
-        // Set ecm as supported
-        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
-
-        processAllMessages();
-
-        emergencyStateTracker.endCall(TEST_CALL_ID);
-
-        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, TEST_CALL_ID, 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.
      */
@@ -901,15 +788,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());
 
@@ -938,16 +825,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
@@ -983,7 +870,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(() -> {
@@ -1003,15 +890,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
@@ -1024,18 +909,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),
@@ -1054,26 +939,28 @@
 
         // 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());
+        verify(phone0, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(phone0, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
 
         // 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),
-                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertFalse(future.isDone());
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(phone0, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
     }
 
     @Test
@@ -1087,7 +974,7 @@
 
         // First emergency call
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1097,7 +984,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),
@@ -1116,7 +1003,7 @@
 
         // First active call
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1125,24 +1012,22 @@
         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.
-        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
-                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertFalse(future.isDone());
 
         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.
@@ -1162,7 +1047,7 @@
                 /* isRadioOn= */ true);
         // Call startEmergencyCall() to set testPhone
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         // Set emergency transport
         emergencyStateTracker.onEmergencyTransportChanged(
@@ -1170,12 +1055,12 @@
 
         // Set call properties
         emergencyStateTracker.onEmergencyCallPropertiesChanged(
-                android.telecom.Connection.PROPERTY_WIFI, TEST_CALL_ID);
+                android.telecom.Connection.PROPERTY_WIFI, mTestConnection1);
 
         verify(testPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
 
         // Set call to ACTIVE
-        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
 
         verify(testPhone, times(1)).cancelEmergencyNetworkScan(eq(true), any());
     }
@@ -1195,7 +1080,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));
@@ -1203,7 +1088,7 @@
 
     @Test
     @SmallTest
-    public void testEndSms() {
+    public void testEndSmsAndExitEmergencyMode() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
         Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
@@ -1216,15 +1101,110 @@
         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, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_CS);
 
         verify(phone0).exitEmergencyMode(any(Message.class));
         assertFalse(emergencyStateTracker.isInEmergencyMode());
+        // CS domain doesn't support SCBM.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testEndSmsAndEnterEmergencySmsCallbackMode() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+
+        verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
+                eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testEndSmsWithSmsSentFailureWhileInScbm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID_2, false);
+        processAllMessages();
+
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // Set emergency transport
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
+
+        // When MO SMS fails while in SCBM.
+        emergencyStateTracker.endSms(TEST_SMS_ID_2, false, DOMAIN_PS);
+
+        verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
+                eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testEndSmsWithSmsSentSuccessWhileInScbm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID_2, false);
+        processAllMessages();
+
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // Set emergency transport
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
+
+        // When MO SMS is successfully sent while in SCBM.
+        emergencyStateTracker.endSms(TEST_SMS_ID_2, true, DOMAIN_PS);
+
+        verify(mCarrierConfigManager, times(2)).getConfigForSubId(anyInt(),
+                eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInScbm());
     }
 
     @Test
@@ -1250,7 +1230,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));
     }
 
@@ -1326,14 +1306,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());
@@ -1355,6 +1335,24 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencySmsWhileInScbm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpScbm(phone0, emergencyStateTracker);
+
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID_2, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        verify(phone0, never()).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
     public void testStartEmergencySmsUsingDifferentPhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1376,6 +1374,36 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencySmsWhileInScbmOnDifferentPhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        Phone phone1 = getPhone(1);
+        setUpAsyncResultForSetEmergencyMode(phone1, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone1,
+                TEST_SMS_ID_2, false);
+
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        // Waits for exiting emergency mode on other phone.
+        assertFalse(future.isDone());
+
+        processAllMessages();
+
+        verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        processAllMessages();
+
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
+    @Test
+    @SmallTest
     public void testStartEmergencyCallActiveAndSmsOnSamePhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1384,14 +1412,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,
@@ -1411,7 +1439,7 @@
                 /* isRadioOn= */ true);
         // Emergency call is in progress.
         CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1438,6 +1466,185 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencyCallWhileCallActiveAndInScbmOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        // Emergency call is in active.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        // Expect: DisconnectCause#NOT_DISCONNECTED
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection2, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallWhileInEcmAndScbmOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setEcmSupportedConfig(phone0, true);
+        // Emergency call is in active.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        // Expect: DisconnectCause#NOT_DISCONNECTED
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        future = emergencyStateTracker.startEmergencyCall(phone0, mTestConnection2, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEcm());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallWhileScbmInProgressOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Emergency call is started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallWhileInScbmOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Emergency call is started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallAndEnterScbmAndEndCallOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setEcmSupportedConfig(phone0, true);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+
+        // Emergency SMS is being started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertFalse(emergencyStateTracker.isInScbm());
+
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+
+        assertTrue(emergencyStateTracker.isInScbm());
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+
+        setEcmSupportedConfig(phone0, false);
+        emergencyStateTracker.endCall(mTestConnection1);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+    }
+
+    @Test
+    @SmallTest
     public void testStartEmergencySmsActiveAndCallOnSamePhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1456,11 +1663,10 @@
                 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));
-        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEmergencyCall());
         // Expect: DisconnectCause#NOT_DISCONNECTED.
@@ -1487,7 +1693,7 @@
 
         // Emergency call is being started.
         CompletableFuture<Integer> callFuture = emergencyStateTracker.startEmergencyCall(phone0,
-                TEST_CALL_ID, false);
+                mTestConnection1, false);
 
         assertFalse(smsFuture.isDone());
         assertFalse(callFuture.isDone());
@@ -1499,7 +1705,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());
@@ -1521,6 +1727,48 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencySmsInProgressAndStartEmergencyCallAndEndSmsOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        // Emergency SMS is in progress.
+        CompletableFuture<Integer> smsFuture = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        ArgumentCaptor<Message> smsCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), smsCaptor.capture());
+
+        // Emergency call is being started.
+        CompletableFuture<Integer> callFuture = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+
+        assertFalse(smsFuture.isDone());
+        assertFalse(callFuture.isDone());
+
+        emergencyStateTracker.endSms(TEST_SMS_ID, false, DOMAIN_PS);
+
+        // Response message for setEmergencyMode by SMS.
+        Message msg = smsCaptor.getValue();
+        AsyncResult.forMessage(msg, E_REG_RESULT, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Response message for setEmergencyMode by call.
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        processAllMessages();
+
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(callFuture.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+    }
+
+    @Test
+    @SmallTest
     public void testStartEmergencyCallAndSmsOnDifferentPhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1529,14 +1777,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);
@@ -1570,7 +1818,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));
@@ -1603,7 +1851,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());
@@ -1615,7 +1863,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());
@@ -1637,6 +1885,73 @@
 
     @Test
     @SmallTest
+    public void testStartEmergencyCallWhileScbmInProgressOnDifferentPhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Emergency call is being started using the different phone.
+        Phone phone1 = getPhone(1);
+        setUpAsyncResultForSetEmergencyMode(phone1, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone1,
+                mTestConnection1, false);
+        processAllMessages();
+
+        // Exit emergency mode and set the emergency mode again by the call when the exit result
+        // is received for obtaining the latest EmergencyRegResult.
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallWhileInScbmOnDifferentPhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        // Emergency SMS is successfully sent and SCBM entered.
+        setUpScbm(phone0, emergencyStateTracker);
+        processAllMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Emergency call is being started using the different phone.
+        Phone phone1 = getPhone(1);
+        setUpAsyncResultForSetEmergencyMode(phone1, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone1,
+                mTestConnection1, false);
+        processAllMessages();
+
+        // Exit emergency mode and set the emergency mode again by the call when the exit result
+        // is received for obtaining the latest EmergencyRegResult.
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // Ensure that SCBM is exited.
+        assertFalse(emergencyStateTracker.isInScbm());
+    }
+
+    @Test
+    @SmallTest
     public void testExitEmergencyModeCallAndSmsOnSamePhone() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1647,14 +1962,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,
@@ -1664,11 +1979,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, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
         processAllMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -1688,14 +2003,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,
@@ -1705,11 +2020,11 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -1729,14 +2044,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,
@@ -1746,13 +2061,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, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
         processAllMessages();
 
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
@@ -1781,14 +2096,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,
@@ -1798,7 +2113,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1807,7 +2122,7 @@
 
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
-        emergencyStateTracker.endSms(TEST_SMS_ID, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
         processAllMessages();
 
         // Enter emergency callback mode and emergency mode changed by SMS end.
@@ -1815,13 +2130,15 @@
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
 
-        // ECM timeout.
+        // ECM/SCBM timeout.
         processAllFutureMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyMode());
         assertFalse(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertFalse(emergencyStateTracker.isInScbm());
         verify(phone0).exitEmergencyMode(any(Message.class));
     }
 
@@ -1837,14 +2154,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,
@@ -1854,11 +2171,11 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
-        emergencyStateTracker.endCall(TEST_CALL_ID);
+        emergencyStateTracker.endCall(mTestConnection1);
         processAllMessages();
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1876,6 +2193,130 @@
 
     @Test
     @SmallTest
+    public void testExitEmergencyModeWhenExitingEcmAndScbm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setEcmSupportedConfig(phone0, true);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+
+        // Emergency SMS is being started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        setScbmTimerValue(phone0, TEST_ECM_EXIT_TIMEOUT_MS + 100);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        processAllMessages();
+
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // ECM timeout.
+        moveTimeForward(TEST_ECM_EXIT_TIMEOUT_MS + 50);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // SCBM timeout.
+        processAllFutureMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInScbm());
+        verify(phone0).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testExitEmergencyModeWhenExitingScbmAndEcm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setEcmSupportedConfig(phone0, true);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, mTestConnection1);
+
+        // Emergency SMS is being started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        setScbmTimerValue(phone0, TEST_ECM_EXIT_TIMEOUT_MS - 100);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        processAllMessages();
+
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.isInScbm());
+
+        // SCBM timeout.
+        moveTimeForward(TEST_ECM_EXIT_TIMEOUT_MS - 50);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        assertFalse(emergencyStateTracker.isInScbm());
+
+        // ECM timeout.
+        processAllFutureMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        verify(phone0).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
     public void testSaveKeyEmergencyCallbackModeSupportedBool() {
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
@@ -1887,7 +2328,7 @@
         EmergencyStateTracker testEst = setupEmergencyStateTracker(
                 false /* isSuplDdsSwitchRequiredForEmergencyCall */);
 
-        assertNotNull(testEst.startEmergencyCall(phone, TEST_CALL_ID, false));
+        assertNotNull(testEst.startEmergencyCall(phone, mTestConnection1, false));
 
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
@@ -1901,14 +2342,14 @@
                 listenerArgumentCaptor.getAllValues().get(0);
 
         // Verify carrier config for valid subscription
-        assertTrue(testEst.isEmergencyCallbackModeSupported());
+        assertTrue(testEst.isEmergencyCallbackModeSupported(phone));
 
         // SIM removed
         when(phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         setEcmSupportedConfig(phone, false);
 
         // Verify default config for invalid subscription
-        assertFalse(testEst.isEmergencyCallbackModeSupported());
+        assertFalse(testEst.isEmergencyCallbackModeSupported(phone));
 
         // Insert SIM again
         when(phone.getSubId()).thenReturn(1);
@@ -1929,7 +2370,7 @@
                 TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
 
         // Verify saved config for valid subscription
-        assertTrue(testEst.isEmergencyCallbackModeSupported());
+        assertTrue(testEst.isEmergencyCallbackModeSupported(phone));
 
         // Insert SIM again, but emergency callback mode not supported
         when(phone.getSubId()).thenReturn(1);
@@ -1941,7 +2382,7 @@
                 TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
 
         // Verify carrier config for valid subscription
-        assertFalse(testEst.isEmergencyCallbackModeSupported());
+        assertFalse(testEst.isEmergencyCallbackModeSupported(phone));
 
         // SIM removed again
         when(phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -1953,7 +2394,169 @@
                 TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
 
         // Verify saved config for valid subscription
-        assertFalse(testEst.isEmergencyCallbackModeSupported());
+        assertFalse(testEst.isEmergencyCallbackModeSupported(phone));
+    }
+
+    /**
+     * 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(
@@ -2007,28 +2610,25 @@
     }
 
     private void setEcmSupportedConfig(Phone phone, boolean ecmSupported) {
-        CarrierConfigManager cfgManager = (CarrierConfigManager) mContext
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        cfgManager.getConfigForSubId(phone.getSubId()).putBoolean(
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId()).putBoolean(
                 CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL,
                 ecmSupported);
     }
 
     private void setConfigForDdsSwitch(Phone phone, String[] roaminPlmns,
             int suplEmergencyModeType, String esExtensionSec) {
-        CarrierConfigManager cfgManager = (CarrierConfigManager) mContext
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        cfgManager.getConfigForSubId(phone.getSubId()).putStringArray(
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId()).putStringArray(
                 CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
                 roaminPlmns);
-        cfgManager.getConfigForSubId(phone.getSubId()).putInt(
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId()).putInt(
                 CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
                 suplEmergencyModeType);
-        cfgManager.getConfigForSubId(phone.getSubId())
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId())
                 .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];
@@ -2047,4 +2647,36 @@
             return null;
         }).when(phone).exitEmergencyMode(any(Message.class));
     }
+
+    private void setUpScbm(Phone phone, EmergencyStateTracker emergencyStateTracker) {
+        setUpAsyncResultForSetEmergencyMode(phone, E_REG_RESULT);
+        setEcmSupportedConfig(phone, true);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        verify(phone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        // Expect: entering SCBM.
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+
+        verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
+                eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
+        if (!emergencyStateTracker.isInEcm() && !emergencyStateTracker.isInEmergencyCall()) {
+            verify(phone).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        }
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInScbm());
+    }
+
+    private void setScbmTimerValue(Phone phone, int millis) {
+        mCarrierConfigManager.getConfigForSubId(phone.getSubId()).putInt(
+                CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, millis);
+    }
 }
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..3ec3ab5 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,36 @@
     }
 
     @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 onUnsupportedOperationExceptionComplete(String message) {
+                        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 +268,100 @@
     }
 
     @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 onUnsupportedOperationExceptionComplete(String message) {
+                        fail("Command should have failed");
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        fail("Command should have succeeded");
+                    }
+                });
+        mLooper.dispatchAll();
+        assertEquals(AVAILABLE_MEMORY, availableMemoryInBytesRef.get().longValue());
+    }
+
+    @Test
+    public void testCommandDispatch_forAvailableMemory_unsupportedOperationException()
+            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.onUnsupportedOperationException("exception message");
+                        return null;
+                    }
+                })
+                .when(mEuiccService)
+                .getAvailableMemoryInBytes(
+                        anyInt(), Mockito.<IGetAvailableMemoryInBytesCallback>any());
+        final AtomicReference<String> exceptionRef = new AtomicReference<>();
+        mConnector.getAvailableMemoryInBytes(
+                CARD_ID,
+                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
+                    @Override
+                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
+                        fail("Command should have failed");
+                    }
+
+                    @Override
+                    public void onUnsupportedOperationExceptionComplete(String message) {
+                        if (exceptionRef.get() != null) {
+                            fail("Callback called twice");
+                        }
+                        exceptionRef.set(message);
+                    }
+
+                    @Override
+                    public void onEuiccServiceUnavailable() {
+                        fail("Command should have succeeded");
+                    }
+                });
+        mLooper.dispatchAll();
+        String message = exceptionRef.get();
+        assertTrue(message != null && !message.isEmpty());
+    }
+
+    @Test
     public void testCommandDispatch_remoteException() throws Exception {
         prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
                 true /* hasPriority */);
@@ -259,6 +385,40 @@
     }
 
     @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 onUnsupportedOperationExceptionComplete(String message) {
+                        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 +448,44 @@
     }
 
     @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 onUnsupportedOperationExceptionComplete(String message) {
+                        fail("Command should have failed");
+                    }
+
+                    @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..57ae9ed 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,89 @@
     }
 
     @Test(expected = SecurityException.class)
+    public void testGetAvailableMemoryInBytes_noPrivileges() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        callGetAvailableMemoryInBytes(AvailableMemoryCallbackStatus.SUCCESS,
+                AVAILABLE_MEMORY, CARD_ID);
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_withPhoneState() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                true /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(AvailableMemoryCallbackStatus.SUCCESS,
+                        AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_withPhoneStatePrivileged() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                true /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(AvailableMemoryCallbackStatus.SUCCESS,
+                        AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_withCarrierPrivileges() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                true /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(AvailableMemoryCallbackStatus.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(AvailableMemoryCallbackStatus.UNAVAILABLE,
+                        AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_exception() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                true /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                false /* hasCarrierPrivileges */);
+        assertThrows(UnsupportedOperationException.class, () -> callGetAvailableMemoryInBytes(
+                AvailableMemoryCallbackStatus.EXCEPTION,
+                AVAILABLE_MEMORY, CARD_ID));
+    }
+
+    @Test
+    public void testGetAvailableMemoryInBytes_unsupportedCardId() throws Exception {
+        setGetAvailableMemoryInBytesPermissions(
+                false /* hasPhoneState */,
+                false /* hasPhoneStatePrivileged */,
+                true /* hasCarrierPrivileges */);
+        assertEquals(
+                AVAILABLE_MEMORY,
+                callGetAvailableMemoryInBytes(
+                        AvailableMemoryCallbackStatus.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 +852,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 +1021,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 +1603,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 +1801,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 +1827,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 +1952,33 @@
         return mController.getEid(cardId, PACKAGE_NAME);
     }
 
+    private long callGetAvailableMemoryInBytes(
+            final AvailableMemoryCallbackStatus status,
+            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 (status == AvailableMemoryCallbackStatus.SUCCESS) {
+                            cb.onGetAvailableMemoryInBytesComplete(availableMemoryInBytes);
+                        } else if (status == AvailableMemoryCallbackStatus.UNAVAILABLE) {
+                            cb.onEuiccServiceUnavailable();
+                        } else if (status == AvailableMemoryCallbackStatus.EXCEPTION) {
+                            cb.onUnsupportedOperationExceptionComplete("exception message");
+                        }
+                        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
@@ -1862,4 +2249,10 @@
         }
         return mController.mExtrasIntent;
     }
+
+    public enum AvailableMemoryCallbackStatus {
+        SUCCESS,
+        EXCEPTION,
+        UNAVAILABLE
+    }
 }
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 b869c9b..47e780e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
@@ -36,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;
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 bfa702c..6056112 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -102,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;
@@ -2497,6 +2497,34 @@
     }
 
     @Test
+    @SmallTest
+    public void testDomainSelectionEmergencyPermFailure() {
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        startOutgoingCall();
+        ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection();
+        mImsCallListener.onCallStartFailed(mSecondImsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_EMERGENCY_PERM_FAILURE, -1));
+        processAllMessages();
+        assertNotNull(c.getImsReasonInfo());
+        assertEquals(ImsReasonInfo.CODE_EMERGENCY_PERM_FAILURE,
+                c.getImsReasonInfo().getCode());
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectionEmergencyTempFailure() {
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        startOutgoingCall();
+        ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection();
+        mImsCallListener.onCallStartFailed(mSecondImsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_EMERGENCY_TEMP_FAILURE, -1));
+        processAllMessages();
+        assertNotNull(c.getImsReasonInfo());
+        assertEquals(ImsReasonInfo.CODE_EMERGENCY_TEMP_FAILURE,
+                c.getImsReasonInfo().getCode());
+    }
+
+    @Test
     public void testUpdateImsCallStatusIncoming() throws Exception {
         // Incoming call
         ImsPhoneConnection connection = setupRingingConnection();
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 21d79a9..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,7 +24,8 @@
 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;
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 a833da0..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,10 +29,11 @@
 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;
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 f491041..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,10 +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.RegistrationManager.SUGGESTED_ACTION_TRIGGER_CLEAR_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;
@@ -63,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;
@@ -82,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;
@@ -819,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());
@@ -1531,13 +1563,13 @@
 
         // unregistered with rat block clear
         registrationCallback.onUnregistered(reasonInfo,
-                SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK,
+                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_BLOCK);
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS);
 
         // reset the registration info saved in the SimulatedCommands
         mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
@@ -1547,13 +1579,13 @@
 
         // verfies that duplicated notification with the same suggested action is invoked
         registrationCallback.onUnregistered(reasonInfo,
-                SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK,
+                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_BLOCK);
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS);
     }
 
     @Test
@@ -1571,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/CellularSecurityTransparencyStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/CellularSecurityTransparencyStatsTest.java
new file mode 100644
index 0000000..2149de0
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/CellularSecurityTransparencyStatsTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.metrics;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.CellularIdentifierDisclosure;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class CellularSecurityTransparencyStatsTest {
+
+    private CellularSecurityTransparencyStats mCellularSecurityStats;
+
+    @Before
+    public void setUp() throws Exception {
+        mCellularSecurityStats = spy(new CellularSecurityTransparencyStats());
+    }
+
+    @Test
+    public void testLogIdentifierDisclosure_NullSimPlmn() {
+        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "123-456", true);
+
+        mCellularSecurityStats.logIdentifierDisclosure(disclosure, null, null, true);
+
+        verify(mCellularSecurityStats).writeIdentifierDisclosure(-1, -1, 123, 456,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
+                true);
+    }
+
+    @Test
+    public void testLogIdentifierDisclosure_badSimPlmn() {
+        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "123-456", true);
+
+        mCellularSecurityStats.logIdentifierDisclosure(disclosure, "INCORRECTLY", "FORMATTED",
+                true);
+
+        verify(mCellularSecurityStats).writeIdentifierDisclosure(-1, -1, 123, 456,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
+                true);
+    }
+
+    @Test
+    public void testLogIdentifierDisclosure_badDisclosurePlmn() {
+        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "INCORRECT", true);
+
+        mCellularSecurityStats.logIdentifierDisclosure(disclosure, "123", "456", true);
+
+        verify(mCellularSecurityStats).writeIdentifierDisclosure(123, 456, -1, -1,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
+                true);
+    }
+
+    @Test
+    public void testLogIdentifierDisclosure_expectedGoodData() {
+        CellularIdentifierDisclosure disclosure = new CellularIdentifierDisclosure(
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI, "999-666", true);
+
+        mCellularSecurityStats.logIdentifierDisclosure(disclosure, "123", "456", true);
+
+        verify(mCellularSecurityStats).writeIdentifierDisclosure(123, 456, 999, 666,
+                CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI,
+                CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST, true,
+                true);
+
+    }
+}
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 df8bd85..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);
     }
 
@@ -373,6 +377,7 @@
         assertEquals(0, stats.simSlotIndex);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
         assertEquals(2000L, stats.unregisteredMillis);
+        assertEquals(0, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -413,6 +418,7 @@
         assertEquals(0, stats.simSlotIndex);
         assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.rat);
         assertEquals(2000L, stats.registeringMillis);
+        assertEquals(0, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -563,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();
@@ -602,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();
@@ -641,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();
@@ -678,6 +687,7 @@
         assertEquals(0L, stats.utAvailableMillis);
         assertEquals(0L, stats.smsCapableMillis);
         assertEquals(0L, stats.smsAvailableMillis);
+        assertEquals(1, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -708,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);
@@ -721,6 +732,7 @@
         assertEquals(0L, statsWifi.utAvailableMillis);
         assertEquals(0L, statsWifi.smsCapableMillis);
         assertEquals(0L, statsWifi.smsAvailableMillis);
+        assertEquals(0, statsWifi.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -741,6 +753,7 @@
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
         assertEquals(0L, stats.registeredMillis);
         assertEquals(2000L, stats.registeringMillis);
+        assertEquals(1, stats.registeredTimes);
     }
 
     @Test
@@ -777,6 +790,7 @@
         assertEquals(0, stats.simSlotIndex);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
         assertEquals(2000L, stats.unregisteredMillis);
+        assertEquals(0, stats.registeredTimes);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -859,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());
@@ -1085,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());
@@ -1119,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);
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 0e1135e..35f1bb7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
@@ -37,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;
@@ -68,8 +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 POWER_CORRELATED_MIN_COOLDOWN_MILLIS =
+            IS_DEBUGGABLE ? 4L *  60L * 1000L : 5L * 3600L * 1000L;
     private static final long MIN_CALLS_PER_BUCKET = 5L;
 
     // NOTE: these fields are currently 32-bit internally and padded to 64-bit by TelephonyManager
@@ -96,6 +98,7 @@
     private UiccPort mActivePort;
     private ServiceStateStats mServiceStateStats;
     private VonrHelper mVonrHelper;
+    private FeatureFlags mFeatureFlags;
 
     private MetricsCollector mMetricsCollector;
 
@@ -109,9 +112,10 @@
         mActivePort = mock(UiccPort.class);
         mServiceStateStats = mock(ServiceStateStats.class);
         mVonrHelper = mock(VonrHelper.class);
+        mFeatureFlags = mock(FeatureFlags.class);
         mMetricsCollector =
                 new MetricsCollector(mContext, mPersistAtomsStorage,
-                        mDeviceStateHelper, mVonrHelper);
+                        mDeviceStateHelper, mVonrHelper, mFeatureFlags);
         doReturn(mSST).when(mSecondPhone).getServiceStateTracker();
         doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
     }
@@ -398,6 +402,9 @@
     @SmallTest
     public void onPullAtom_cellularServiceState_tooFrequent() throws Exception {
         doReturn(null).when(mPersistAtomsStorage).getCellularServiceStates(anyLong());
+        mContextFixture.putIntResource(
+                com.android.internal.R.integer.config_metrics_pull_cooldown_millis,
+                (int) POWER_CORRELATED_MIN_COOLDOWN_MILLIS);
         List<StatsEvent> actualAtoms = new ArrayList<>();
 
         int result = mMetricsCollector.onPullAtom(CELLULAR_SERVICE_STATE, actualAtoms);
@@ -405,7 +412,7 @@
         assertThat(actualAtoms).hasSize(0);
         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
         verify(mPersistAtomsStorage, times(1)).getCellularServiceStates(
-                eq(CELL_SERVICE_MIN_COOLDOWN_MILLIS));
+                eq(POWER_CORRELATED_MIN_COOLDOWN_MILLIS));
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
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 0cbb0f6..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
@@ -1143,6 +1193,8 @@
         mServiceState5Proto = null;
         mServiceSwitches = null;
         mServiceStates = null;
+        mImsRegStatsUnregisteredLte0 = null;
+        mImsRegStatsRegisteringLte0 = null;
         mImsRegistrationStatsLte0 = null;
         mImsRegistrationStatsWifi0 = null;
         mImsRegistrationStatsLte1 = null;
@@ -1752,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);
@@ -1768,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(
@@ -1778,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;
@@ -1801,10 +1869,12 @@
         assertProtoArrayEqualsIgnoringOrder(
                 new ImsRegistrationStats[] {
                     newImsRegistrationStatsLte0,
+                    mImsRegStatsRegisteringLte0,
+                    mImsRegistrationStatsLte0,
                     mImsRegistrationStatsWifi0,
                     mImsRegistrationStatsLte1
                 },
-                serviceStates);
+                regStats);
     }
 
     @Test
@@ -1833,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();
 
@@ -1942,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);
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/ServiceStateStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
index c66cfa7..16dab44 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
@@ -45,7 +45,8 @@
 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;
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 c5c672c..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,7 +23,7 @@
 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;
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 1498eb4..58cc516 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
@@ -58,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;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VonrHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VonrHelperTest.java
index 04cd925..ddc29de 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VonrHelperTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VonrHelperTest.java
@@ -21,7 +21,8 @@
 import static org.mockito.Mockito.doReturn;
 
 import android.annotation.NonNull;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.flags.FeatureFlags;
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 fd5f6ab..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,6 +16,8 @@
 
 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;
@@ -29,6 +31,7 @@
 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;
@@ -82,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;
@@ -155,7 +162,7 @@
     }
 
     @Test
-    public void testSendSatelliteDatagram_usingSatelliteModemInterface_success() throws  Exception {
+    public void testSendSatelliteDatagram_success() throws  Exception {
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[3];
 
@@ -168,7 +175,7 @@
         doReturn(true).when(mMockDatagramController)
                 .needsWaitingForSatelliteConnected();
         when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
-                .thenReturn(DatagramController.DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT);
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
         mResultListener.clear();
 
         mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
@@ -221,7 +228,7 @@
         mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
         assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        moveTimeForward(DatagramController.DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT);
+        moveTimeForward(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
         processAllMessages();
         verifyZeroInteractions(mMockSatelliteModemInterface);
         mInOrder.verify(mMockDatagramController)
@@ -267,7 +274,87 @@
     }
 
     @Test
-    public void testSendSatelliteDatagram_usingSatelliteModemInterface_failure() throws  Exception {
+    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];
 
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 0e16e25..3a42881 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -78,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;
@@ -163,7 +165,7 @@
         }).when(mMockSatelliteModemInterface).pollPendingSatelliteDatagrams(any(Message.class));
         doReturn(true).when(mMockDatagramController).needsWaitingForSatelliteConnected();
         when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
-                .thenReturn(DatagramController.DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT);
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
         mResultListener.clear();
 
         mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
@@ -201,7 +203,7 @@
         verifyZeroInteractions(mMockSatelliteModemInterface);
         assertTrue(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        moveTimeForward(DatagramController.DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT);
+        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),
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 0944c6c..f8827be 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
@@ -76,7 +76,7 @@
         replaceInstance(SatelliteController.class, "sInstance", null,
                 mMockSatelliteController);
         doReturn(Arrays.asList(SATELLITE_PLMN_ARRAY))
-                .when(mMockSatelliteController).getSatellitePlmnList(anyInt());
+                .when(mMockSatelliteController).getSatellitePlmnsForCarrier(anyInt());
         doReturn(mSatelliteSupportedServiceList).when(mMockSatelliteController)
                 .getSupportedSatelliteServices(SUB_ID, SATELLITE_PLMN);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java
new file mode 100644
index 0000000..e4f0255
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.satellite;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.testing.AndroidTestingRunner;
+
+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.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+
+public class SatelliteConfigParserTest extends TelephonyTest {
+
+    /**
+     * satelliteConfigBuilder.setVersion(4);
+     *
+     * carrierSupportedSatelliteServiceBuilder.setCarrierId(1);
+     *
+     * satelliteProviderCapabilityBuilder.setCarrierPlmn("310160");
+     * satelliteProviderCapabilityBuilder.addAllowedServices(1);
+     * satelliteProviderCapabilityBuilder.addAllowedServices(2);
+     * satelliteProviderCapabilityBuilder.addAllowedServices(3);
+     *
+     * satelliteProviderCapabilityBuilder.setCarrierPlmn("310220");
+     * satelliteProviderCapabilityBuilder.addAllowedServices(3);
+     *
+     * String test = "0123456789";
+     * bigString.append(test.repeat(1));
+     * satelliteRegionBuilder.setS2CellFile(ByteString.copyFrom(bigString.toString().getBytes()));
+     * satelliteRegionBuilder.addCountryCodes("US");
+     * satelliteRegionBuilder.setIsAllowed(true);
+     */
+    private String mBase64StrForPBByteArray =
+            "CjYIBBIeCAESDgoGMzEwMTYwEAEQAhADEgoKBjMxMDIyMBADGhIKCjAxMjM0NTY3ODkSAlVTGAE=";
+    private byte[] mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
+
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd(TAG + " tearDown");
+        super.tearDown();
+    }
+
+    @Test
+    public void testGetAllSatellitePlmnsForCarrier() {
+        List<String> compareList_cid1 = new ArrayList<>();
+        compareList_cid1.add("310160");
+        compareList_cid1.add("310220");
+        List<String> compareList_cid_placeholder = new ArrayList<>();
+        compareList_cid_placeholder.add("310260");
+        compareList_cid_placeholder.add("45060");
+
+
+        SatelliteConfigParser satelliteConfigParserNull = new SatelliteConfigParser((byte[]) null);
+        assertNotNull(satelliteConfigParserNull);
+        assertNull(satelliteConfigParserNull.getConfig());
+
+        SatelliteConfigParser satelliteConfigParserPlaceHolder =
+                new SatelliteConfigParser((byte[]) null);
+        assertNotNull(satelliteConfigParserPlaceHolder);
+        assertNull(satelliteConfigParserPlaceHolder.getConfig());
+
+        SatelliteConfigParser satelliteConfigParser = new SatelliteConfigParser(mBytesProtoBuffer);
+
+        List<String> parsedList1 = satelliteConfigParser.getConfig()
+                .getAllSatellitePlmnsForCarrier(1);
+        Collections.sort(compareList_cid1);
+        Collections.sort(compareList_cid_placeholder);
+        Collections.sort(parsedList1);
+
+        assertEquals(compareList_cid1, parsedList1);
+        assertNotEquals(compareList_cid_placeholder, parsedList1);
+
+        List<String> parsedList2 = satelliteConfigParser.getConfig()
+                .getAllSatellitePlmnsForCarrier(0);
+        assertEquals(0, parsedList2.size());
+    }
+
+    @Test
+    public void testGetSupportedSatelliteServices() {
+        Map<String, Set<Integer>> compareMapCarrierId1 = new HashMap<>();
+        Set<Integer> compareSet310160 = new HashSet<>();
+        compareSet310160.add(1);
+        compareSet310160.add(2);
+        compareSet310160.add(3);
+        compareMapCarrierId1.put("310160", compareSet310160);
+
+        Set<Integer> compareSet310220 = new HashSet<>();
+        compareSet310220.add(3);
+        compareMapCarrierId1.put("310220", compareSet310220);
+
+        SatelliteConfigParser satelliteConfigParserNull = new SatelliteConfigParser((byte[]) null);
+        assertNotNull(satelliteConfigParserNull);
+        assertNull(satelliteConfigParserNull.getConfig());
+
+        SatelliteConfigParser satelliteConfigParserPlaceholder =
+                new SatelliteConfigParser("test".getBytes());
+        assertNotNull(satelliteConfigParserPlaceholder);
+        assertNull(satelliteConfigParserPlaceholder.getConfig());
+
+        SatelliteConfigParser satelliteConfigParser = new SatelliteConfigParser(mBytesProtoBuffer);
+        Map<String, Set<Integer>> parsedMap1 = satelliteConfigParser.getConfig()
+                .getSupportedSatelliteServices(0);
+        Map<String, Set<Integer>> parsedMap2 = satelliteConfigParser.getConfig()
+                .getSupportedSatelliteServices(1);
+        assertEquals(0, parsedMap1.size());
+        assertEquals(2, parsedMap2.size());
+        assertEquals(compareMapCarrierId1, parsedMap2);
+    }
+
+    @Test
+    public void testGetDeviceSatelliteCountryCodes() {
+        List<String> compareList_countryCodes = new ArrayList<>();
+        compareList_countryCodes.add("US");
+        Collections.sort(compareList_countryCodes);
+
+        List<String> compareList_countryCodes_placeholder = new ArrayList<>();
+        compareList_countryCodes_placeholder.add("US");
+        compareList_countryCodes_placeholder.add("IN");
+        Collections.sort(compareList_countryCodes_placeholder);
+
+        SatelliteConfigParser satelliteConfigParserNull = new SatelliteConfigParser((byte[]) null);
+        assertNotNull(satelliteConfigParserNull);
+        assertNull(satelliteConfigParserNull.getConfig());
+
+        SatelliteConfigParser satelliteConfigParser = new SatelliteConfigParser(mBytesProtoBuffer);
+        List<String> tempList = satelliteConfigParser.getConfig().getDeviceSatelliteCountryCodes();
+        List<String> parsedList = new ArrayList<>(tempList);
+        Collections.sort(parsedList);
+
+        assertEquals(compareList_countryCodes, parsedList);
+        assertNotEquals(compareList_countryCodes_placeholder, parsedList);
+    }
+
+    @Test
+    public void testGetSatelliteS2CellFile() {
+        final String filePath = "/data/user_de/0/com.android.phone/app_satellite/s2_cell_file";
+        Path targetSatS2FilePath = Paths.get(filePath);
+
+        SatelliteConfigParser mockedSatelliteConfigParserNull = spy(
+                new SatelliteConfigParser((byte[]) null));
+        assertNotNull(mockedSatelliteConfigParserNull);
+        assertNull(mockedSatelliteConfigParserNull.getConfig());
+
+        SatelliteConfigParser mockedSatelliteConfigParserPlaceholder = spy(
+                new SatelliteConfigParser("test".getBytes()));
+        assertNotNull(mockedSatelliteConfigParserPlaceholder);
+        assertNull(mockedSatelliteConfigParserPlaceholder.getConfig());
+
+        SatelliteConfigParser mockedSatelliteConfigParser =
+                spy(new SatelliteConfigParser(mBytesProtoBuffer));
+        SatelliteConfig mockedSatelliteConfig = Mockito.mock(SatelliteConfig.class);
+        doReturn(targetSatS2FilePath).when(mockedSatelliteConfig).getSatelliteS2CellFile(any());
+        doReturn(mockedSatelliteConfig).when(mockedSatelliteConfigParser).getConfig();
+//        assertNotNull(mockedSatelliteConfigParser.getConfig());
+//        doReturn(false).when(mockedSatelliteConfigParser).getConfig().isFileExist(any());
+//        doReturn(targetSatS2FilePath).when(mockedSatelliteConfigParser).getConfig()
+//                .copySatS2FileToPhoneDirectory(any(), any());
+        assertEquals(targetSatS2FilePath,
+                mockedSatelliteConfigParser.getConfig().getSatelliteS2CellFile(mContext));
+    }
+
+    @Test
+    public void testGetSatelliteAccessAllow() {
+        SatelliteConfigParser satelliteConfigParserNull = new SatelliteConfigParser((byte[]) null);
+        assertNotNull(satelliteConfigParserNull);
+        assertNull(satelliteConfigParserNull.getConfig());
+
+        SatelliteConfigParser satelliteConfigParserPlaceholder =
+                new SatelliteConfigParser("test".getBytes());
+        assertNotNull(satelliteConfigParserPlaceholder);
+        assertNull(satelliteConfigParserPlaceholder.getConfig());
+
+        SatelliteConfigParser satelliteConfigParser = new SatelliteConfigParser(mBytesProtoBuffer);
+        assertNotNull(satelliteConfigParser);
+        assertNotNull(satelliteConfigParser.getConfig());
+        assertTrue(satelliteConfigParser.getConfig().isSatelliteDataForAllowedRegion());
+    }
+}
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 55532cc..aae6a2f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -18,6 +18,7 @@
 
 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;
@@ -34,6 +35,7 @@
 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_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;
@@ -44,6 +46,7 @@
 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;
@@ -82,6 +85,7 @@
 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;
@@ -94,15 +98,14 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.ServiceSpecificException;
 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;
@@ -112,6 +115,8 @@
 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;
@@ -119,6 +124,8 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.configupdate.ConfigProviderAdaptor;
+import com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
@@ -129,6 +136,7 @@
 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;
@@ -157,7 +165,11 @@
     private static final String TEST_NEXT_SATELLITE_TOKEN = "TEST_NEXT_SATELLITE_TOKEN";
     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<>();
 
@@ -174,10 +186,15 @@
     @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;
+    @Mock private TelephonyConfigUpdateInstallReceiver mMockTelephonyConfigUpdateInstallReceiver;
+    @Mock private SatelliteConfigParser mMockConfigParser;
+    @Mock private SatelliteConfig mMockConfig;
+
     private Semaphore mIIntegerConsumerSemaphore = new Semaphore(0);
     private IIntegerConsumer mIIntegerConsumer = new IIntegerConsumer.Stub() {
         @Override
@@ -212,6 +229,7 @@
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedSatelliteCapabilitiesResultCode = resultCode;
+            logd("mSatelliteCapabilitiesReceiver: resultCode=" + resultCode);
             if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_CAPABILITIES)) {
                     mQueriedSatelliteCapabilities = resultData.getParcelable(
@@ -221,13 +239,13 @@
                     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);
             }
         }
     };
@@ -239,6 +257,7 @@
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedSatelliteSupportedResultCode = resultCode;
+            logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
             if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_SUPPORTED)) {
                     mQueriedSatelliteSupported = resultData.getBoolean(KEY_SATELLITE_SUPPORTED);
@@ -247,7 +266,6 @@
                     mQueriedSatelliteSupported = false;
                 }
             } else {
-                logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
                 mQueriedSatelliteSupported = false;
             }
             try {
@@ -279,7 +297,7 @@
             try {
                 mIsSatelliteEnabledSemaphore.release();
             } catch (Exception ex) {
-                loge("mIsSatelliteEnableReceiver: Got exception in releasing semaphore, ex=" + ex);
+                loge("mIsSatelliteEnabledReceiver: Got exception in releasing semaphore, ex=" + ex);
             }
         }
     };
@@ -291,6 +309,7 @@
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedIsDemoModeEnabledResultCode = resultCode;
+            logd("mIsDemoModeEnabledReceiver: resultCode=" + resultCode);
             if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_DEMO_MODE_ENABLED)) {
                     mQueriedIsDemoModeEnabled = resultData.getBoolean(KEY_DEMO_MODE_ENABLED);
@@ -299,7 +318,6 @@
                     mQueriedIsDemoModeEnabled = false;
                 }
             } else {
-                logd("mIsSatelliteEnableReceiver: resultCode=" + resultCode);
                 mQueriedIsDemoModeEnabled = false;
             }
             try {
@@ -317,6 +335,7 @@
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedIsSatelliteProvisionedResultCode = resultCode;
+            logd("mIsSatelliteProvisionedReceiver: resultCode=" + resultCode);
             if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) {
                     mQueriedIsSatelliteProvisioned =
@@ -344,6 +363,7 @@
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedSatelliteAllowedResultCode = resultCode;
+            logd("mSatelliteAllowedReceiver: resultCode=" + resultCode);
             if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
                     mQueriedSatelliteAllowed = resultData.getBoolean(
@@ -353,7 +373,6 @@
                     mQueriedSatelliteAllowed = false;
                 }
             } else {
-                logd("mSatelliteAllowedReceiver: resultCode=" + resultCode);
                 mQueriedSatelliteAllowed = false;
             }
             try {
@@ -372,6 +391,7 @@
         @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
             mQueriedSatelliteVisibilityTimeResultCode = resultCode;
+            logd("mSatelliteVisibilityTimeReceiver: resultCode=" + resultCode);
             if (resultCode == SATELLITE_RESULT_SUCCESS) {
                 if (resultData.containsKey(KEY_SATELLITE_NEXT_VISIBILITY)) {
                     mQueriedSatelliteVisibilityTime = resultData.getInt(
@@ -381,13 +401,13 @@
                     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);
             }
         }
     };
@@ -400,6 +420,7 @@
         @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);
@@ -410,7 +431,6 @@
                     mQueriedNtnSignalStrengthLevel = NTN_SIGNAL_STRENGTH_NONE;
                 }
             } else {
-                logd("KEY_NTN_SIGNAL_STRENGTH: resultCode=" + resultCode);
                 mQueriedNtnSignalStrengthLevel = NTN_SIGNAL_STRENGTH_NONE;
             }
             try {
@@ -445,6 +465,8 @@
         replaceInstance(SubscriptionManagerService.class, "sInstance", null,
                 mMockSubscriptionManagerService);
         replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mPhone2});
+        replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance",
+                null, mMockTelephonyConfigUpdateInstallReceiver);
 
         mServiceState2 = Mockito.mock(ServiceState.class);
         when(mPhone.getServiceState()).thenReturn(mServiceState);
@@ -455,6 +477,9 @@
         mContextFixture.putStringArrayResource(
                 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();
@@ -498,6 +523,13 @@
         doNothing().when(mMockProvisionMetricsStats).reportProvisionMetrics();
         doNothing().when(mMockControllerMetricsStats).reportDeprovisionCount(anyInt());
         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(
@@ -512,6 +544,9 @@
                 any(Handler.class),
                 eq(28) /* EVENT_SATELLITE_MODEM_STATE_CHANGED */,
                 eq(null));
+
+        doReturn(mMockConfigParser).when(mMockTelephonyConfigUpdateInstallReceiver)
+                .getConfigParser(ConfigProviderAdaptor.DOMAIN_SATELLITE);
     }
 
     @After
@@ -619,10 +654,7 @@
                 mQueriedSatelliteVisibilityTimeResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        provisionSatelliteService();
         setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
                 SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
@@ -633,10 +665,7 @@
         assertEquals(mSatelliteNextVisibilityTime, mQueriedSatelliteVisibilityTime);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        provisionSatelliteService();
         setUpNullResponseForRequestTimeForNextSatelliteVisibility(
                 SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
@@ -647,10 +676,7 @@
                 mQueriedSatelliteVisibilityTimeResultCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        provisionSatelliteService();
         setUpNullResponseForRequestTimeForNextSatelliteVisibility(
                 SATELLITE_RESULT_INVALID_MODEM_STATE);
         mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
@@ -662,6 +688,55 @@
     }
 
     @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
     public void testRequestSatelliteEnabled() {
         mIsSatelliteEnabledSemaphore.drainPermits();
 
@@ -717,7 +792,6 @@
         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();
 
@@ -776,7 +850,6 @@
         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));
@@ -1009,11 +1082,8 @@
                 (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
+        provisionSatelliteService();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
@@ -1026,11 +1096,8 @@
         verify(mMockPointingAppController).setStartedSatelliteTransmissionUpdates(eq(true));
 
         resetSatelliteControllerUT();
+        provisionSatelliteService();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
         mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
@@ -1090,10 +1157,7 @@
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        provisionSatelliteService();
         setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStopTransmissionUpdateCallback);
@@ -1105,11 +1169,8 @@
         verify(mMockPointingAppController).stopSatelliteTransmissionUpdates(any(Message.class));
 
         resetSatelliteControllerUT();
+        provisionSatelliteService();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
         mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
                 mStopTransmissionUpdateCallback);
@@ -1156,10 +1217,7 @@
 
         resetSatelliteControllerUT();
         boolean isDemoModeEnabled = mSatelliteControllerUT.isDemoModeEnabled();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        provisionSatelliteService();
         mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedIsDemoModeEnabledResultCode);
@@ -1168,12 +1226,21 @@
 
     @Test
     public void testIsSatelliteEnabled() {
-        assertFalse(mSatelliteControllerUT.isSatelliteEnabled());
         setUpResponseForRequestIsSatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+        assertFalse(mSatelliteControllerUT.isSatelliteEnabled());
         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);
     }
 
@@ -1187,17 +1254,18 @@
         setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
 
+        setUpResponseForRequestIsSatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.onSatelliteServiceConnected();
         processAllMessages();
 
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, 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);
@@ -1219,19 +1287,19 @@
 
     @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);
     }
 
@@ -1293,7 +1361,7 @@
                 };
         when(mMockDatagramController.registerForSatelliteDatagram(eq(SUB_ID), eq(callback)))
                 .thenReturn(SATELLITE_RESULT_SUCCESS);
-        int errorCode = mSatelliteControllerUT.registerForSatelliteDatagram(SUB_ID, callback);
+        int errorCode = mSatelliteControllerUT.registerForIncomingDatagram(SUB_ID, callback);
         assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
         verify(mMockDatagramController).registerForSatelliteDatagram(eq(SUB_ID), eq(callback));
     }
@@ -1311,7 +1379,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));
     }
 
@@ -1321,7 +1389,7 @@
         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));
@@ -1337,7 +1405,7 @@
         sendProvisionedStateChangedEvent(false, null);
         processAllMessages();
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID,
+        mSatelliteControllerUT.sendDatagram(SUB_ID,
                 SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1351,7 +1419,7 @@
         sendProvisionedStateChangedEvent(true, null);
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID,
+        mSatelliteControllerUT.sendDatagram(SUB_ID,
                 SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
@@ -1364,7 +1432,7 @@
     @Test
     public void testPollPendingSatelliteDatagrams() {
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer);
+        mSatelliteControllerUT.pollPendingDatagrams(SUB_ID, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
@@ -1377,7 +1445,7 @@
         sendProvisionedStateChangedEvent(false, null);
         processAllMessages();
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer);
+        mSatelliteControllerUT.pollPendingDatagrams(SUB_ID, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
@@ -1388,7 +1456,7 @@
         sendProvisionedStateChangedEvent(true, null);
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer);
+        mSatelliteControllerUT.pollPendingDatagrams(SUB_ID, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
         verify(mMockDatagramController, times(1)).pollPendingSatelliteDatagrams(anyInt(), any());
@@ -1425,20 +1493,6 @@
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, 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));
-        assertNull(cancelRemote);
-
-        resetSatelliteControllerUT();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
@@ -1451,6 +1505,47 @@
         assertTrue(waitForIIntegerConsumerResult(1));
         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();
@@ -1554,24 +1649,32 @@
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
+        provisionSatelliteService();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(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_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();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN,
                 SATELLITE_RESULT_INVALID_MODEM_STATE);
         mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
@@ -1579,12 +1682,14 @@
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
     }
 
     @Test
     public void testSupportedSatelliteServices() {
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
-        List<String> satellitePlmnList = mSatelliteControllerUT.getSatellitePlmnList(SUB_ID);
+        List<String> satellitePlmnList = mSatelliteControllerUT.getSatellitePlmnsForCarrier(
+                SUB_ID);
         assertEquals(EMPTY_STRING_ARRAY.length, satellitePlmnList.size());
         List<Integer> supportedSatelliteServices =
                 mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101");
@@ -1607,7 +1712,7 @@
         TestSatelliteController testSatelliteController =
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
 
-        satellitePlmnList = testSatelliteController.getSatellitePlmnList(SUB_ID);
+        satellitePlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
         assertTrue(satellitePlmnList.isEmpty());
         supportedSatelliteServices =
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
@@ -1631,6 +1736,8 @@
 
         // Trigger carrier config changed with carrierEnabledSatelliteFlag enabled
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
         for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
                 : mCarrierConfigChangedListenerList) {
             pair.first.execute(() -> pair.second.onCarrierConfigChanged(
@@ -1639,7 +1746,7 @@
         }
         processAllMessages();
 
-        satellitePlmnList = testSatelliteController.getSatellitePlmnList(SUB_ID);
+        satellitePlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
         assertTrue(Arrays.equals(
                 expectedSupportedSatellitePlmns, satellitePlmnList.stream().toArray()));
         supportedSatelliteServices =
@@ -1666,7 +1773,7 @@
         }
         processAllMessages();
 
-        satellitePlmnList = testSatelliteController.getSatellitePlmnList(SUB_ID);
+        satellitePlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
         assertTrue(satellitePlmnList.isEmpty());
         supportedSatelliteServices =
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
@@ -1711,7 +1818,8 @@
         TestSatelliteController testSatelliteController =
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
         processAllMessages();
-        List<String> carrierPlmnList = testSatelliteController.getSatellitePlmnList(SUB_ID);
+        List<String> carrierPlmnList = testSatelliteController.getSatellitePlmnsForCarrier(
+                SUB_ID);
         verify(mMockSatelliteModemInterface, never()).setSatellitePlmn(
                 anyInt(), anyList(), anyList(), any(Message.class));
         assertTrue(carrierPlmnList.isEmpty());
@@ -1738,7 +1846,7 @@
             );
         }
         processAllMessages();
-        carrierPlmnList = testSatelliteController.getSatellitePlmnList(SUB_ID);
+        carrierPlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
         verify(mMockSatelliteModemInterface, never()).setSatellitePlmn(
                 anyInt(), anyList(), anyList(), any(Message.class));
         assertTrue(carrierPlmnList.isEmpty());
@@ -1764,7 +1872,7 @@
         }
         processAllMessages();
 
-        carrierPlmnList = testSatelliteController.getSatellitePlmnList(SUB_ID);
+        carrierPlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
         assertTrue(carrierPlmnList.isEmpty());
         List<String> allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
                 carrierPlmnList, satellitePlmnListFromOverlayConfig);
@@ -1784,7 +1892,7 @@
             );
         }
         processAllMessages();
-        carrierPlmnList = testSatelliteController.getSatellitePlmnList(SUB_ID);
+        carrierPlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
         allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
                 carrierPlmnList, satellitePlmnListFromOverlayConfig);
         assertEquals(expectedCarrierPlmnList, carrierPlmnList);
@@ -1823,7 +1931,7 @@
             );
         }
         processAllMessages();
-        carrierPlmnList = testSatelliteController.getSatellitePlmnList(SUB_ID);
+        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));
@@ -1849,9 +1957,9 @@
         setUpResponseForRequestSetSatelliteEnabledForCarrier(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        mSatelliteControllerUT.removeSatelliteAttachRestrictionForCarrier(SUB_ID,
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
                 SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
-        mSatelliteControllerUT.removeSatelliteAttachRestrictionForCarrier(SUB_ID,
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
                 SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(2));
@@ -1860,7 +1968,7 @@
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(1));
 
         Set<Integer> restrictionSet =
-                mSatelliteControllerUT.getSatelliteAttachRestrictionReasonsForCarrier(SUB_ID);
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
         assertTrue(!restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
         assertTrue(!restrictionSet.contains(
                 SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION));
@@ -1870,7 +1978,7 @@
         reset(mMockSatelliteModemInterface);
         setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        mSatelliteControllerUT.addSatelliteAttachRestrictionForCarrier(SUB_ID,
+        mSatelliteControllerUT.addAttachRestrictionForCarrier(SUB_ID,
                 SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
         processAllMessages();
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -1878,7 +1986,7 @@
                 .requestSetSatelliteEnabledForCarrier(anyInt(), anyBoolean(), any(Message.class));
         assertTrue(waitForIIntegerConsumerResult(1));
         restrictionSet =
-                mSatelliteControllerUT.getSatelliteAttachRestrictionReasonsForCarrier(SUB_ID);
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
         assertTrue(restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
 
         // remove satellite restriction reason by user
@@ -1886,13 +1994,13 @@
         reset(mMockSatelliteModemInterface);
         setUpResponseForRequestSetSatelliteEnabledForCarrier(true, SATELLITE_RESULT_SUCCESS);
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        mSatelliteControllerUT.removeSatelliteAttachRestrictionForCarrier(SUB_ID,
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
                 SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
         restrictionSet =
-                mSatelliteControllerUT.getSatelliteAttachRestrictionReasonsForCarrier(SUB_ID);
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
         assertTrue(!restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
         verify(mMockSatelliteModemInterface, times(1))
                 .requestSetSatelliteEnabledForCarrier(anyInt(), anyBoolean(), any(Message.class));
@@ -1902,13 +2010,13 @@
         reset(mMockSatelliteModemInterface);
         setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        mSatelliteControllerUT.addSatelliteAttachRestrictionForCarrier(SUB_ID,
+        mSatelliteControllerUT.addAttachRestrictionForCarrier(SUB_ID,
                 SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
         restrictionSet =
-                mSatelliteControllerUT.getSatelliteAttachRestrictionReasonsForCarrier(SUB_ID);
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
         assertTrue(restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
         verify(mMockSatelliteModemInterface, times(1))
                 .requestSetSatelliteEnabledForCarrier(anyInt(), eq(false), any(Message.class));
@@ -1917,14 +2025,14 @@
         mIIntegerConsumerResults.clear();
         reset(mMockSatelliteModemInterface);
         setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.addSatelliteAttachRestrictionForCarrier(SUB_ID,
+        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.getSatelliteAttachRestrictionReasonsForCarrier(SUB_ID);
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
         assertTrue(restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION));
         verify(mMockSatelliteModemInterface, never())
                 .requestSetSatelliteEnabledForCarrier(anyInt(), anyBoolean(), any(Message.class));
@@ -1933,14 +2041,14 @@
         mIIntegerConsumerResults.clear();
         reset(mMockSatelliteModemInterface);
         setUpResponseForRequestSetSatelliteEnabledForCarrier(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.removeSatelliteAttachRestrictionForCarrier(SUB_ID,
+        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.getSatelliteAttachRestrictionReasonsForCarrier(SUB_ID);
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
         assertTrue(!restrictionSet.contains(
                 SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION));
         verify(mMockSatelliteModemInterface, never())
@@ -1950,14 +2058,14 @@
         mIIntegerConsumerResults.clear();
         reset(mMockSatelliteModemInterface);
         setUpResponseForRequestSetSatelliteEnabledForCarrier(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.removeSatelliteAttachRestrictionForCarrier(SUB_ID,
+        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.getSatelliteAttachRestrictionReasonsForCarrier(SUB_ID);
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
         assertTrue(!restrictionSet.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER));
         verify(mMockSatelliteModemInterface, times(1))
                 .requestSetSatelliteEnabledForCarrier(anyInt(), eq(true), any(Message.class));
@@ -1966,7 +2074,7 @@
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
 
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.removeSatelliteAttachRestrictionForCarrier(SUB_ID,
+        mSatelliteControllerUT.removeAttachRestrictionForCarrier(SUB_ID,
                 SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1975,7 +2083,7 @@
         verifyZeroInteractions(mMockSatelliteModemInterface);
 
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.addSatelliteAttachRestrictionForCarrier(SUB_ID,
+        mSatelliteControllerUT.addAttachRestrictionForCarrier(SUB_ID,
                 SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1984,15 +2092,15 @@
         verifyZeroInteractions(mMockSatelliteModemInterface);
 
         Set<Integer> satelliteRestrictionReasons =
-                mSatelliteControllerUT.getSatelliteAttachRestrictionReasonsForCarrier(SUB_ID);
+                mSatelliteControllerUT.getAttachRestrictionReasonsForCarrier(SUB_ID);
         assertTrue(satelliteRestrictionReasons.isEmpty());
     }
 
     @Test
     public void testIsSatelliteAttachRequired() {
-        mSatelliteCapabilitiesSemaphore.drainPermits();
         TestSatelliteController satelliteController =
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        mSatelliteCapabilitiesSemaphore.drainPermits();
         satelliteController.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
@@ -2002,12 +2110,11 @@
 
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestSatelliteCapabilities(
-                mSatelliteCapabilities, SATELLITE_RESULT_MODEM_ERROR);
+                mEmptySatelliteCapabilities, SATELLITE_RESULT_SUCCESS);
         satelliteController =
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestSatelliteCapabilities(
-                mEmptySatelliteCapabilities, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(satelliteController, true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteCapabilitiesSemaphore.drainPermits();
         satelliteController.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
@@ -2020,12 +2127,11 @@
 
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestSatelliteCapabilities(
-                mSatelliteCapabilities, SATELLITE_RESULT_MODEM_ERROR);
+                mSatelliteCapabilities, SATELLITE_RESULT_SUCCESS);
         satelliteController =
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestSatelliteCapabilities(
-                mSatelliteCapabilities, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(satelliteController, true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteCapabilitiesSemaphore.drainPermits();
         satelliteController.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
@@ -2082,9 +2188,7 @@
         verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_NONE, SATELLITE_RESULT_NOT_SUPPORTED);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        provisionSatelliteService();
 
         doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
@@ -2096,8 +2200,7 @@
         verifyRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        provisionSatelliteService();
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         // reset cache to NTN_SIGNAL_STRENGTH_NONE
         sendNtnSignalStrengthChangedEvent(NTN_SIGNAL_STRENGTH_NONE, null);
@@ -2172,10 +2275,8 @@
         verifyRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_NOT_SUPPORTED);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        provisionSatelliteService();
         verifyRegisterForNtnSignalStrengthChanged(SUB_ID, callback,
                 SATELLITE_RESULT_SUCCESS);
         verifyRequestNtnSignalStrength(expectedLevel, SATELLITE_RESULT_SUCCESS);
@@ -2265,10 +2366,7 @@
         // startSendingNtnSignalStrength() is requested when screen on event comes.
         reset(mMockSatelliteModemInterface);
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        setUpResponseForRequestIsSatelliteSupported(true, expectedResult);
-        setUpResponseForRequestIsSatelliteProvisioned(true, expectedResult);
-        verifySatelliteSupported(true, expectedResult);
-        verifySatelliteProvisioned(true, expectedResult);
+        provisionSatelliteService();
         setUpResponseForStartSendingNtnSignalStrength(expectedResult);
         sendCmdStartSendingNtnSignalStrengthChangedEvent(true);
         processAllMessages();
@@ -2462,23 +2560,20 @@
                     }
                 };
 
-        int errorCode = mSatelliteControllerUT.registerForSatelliteCapabilitiesChanged(SUB_ID,
+        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.registerForSatelliteCapabilitiesChanged(SUB_ID,
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
                 callback);
         assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, errorCode);
 
         resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteProvisioned(true,
-                SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        errorCode = mSatelliteControllerUT.registerForSatelliteCapabilitiesChanged(SUB_ID,
+        provisionSatelliteService();
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
                 callback);
         assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
         SatelliteCapabilities expectedCapabilities = mSatelliteCapabilities;
@@ -2495,7 +2590,7 @@
                 semaphore, 1, "testRegisterForSatelliteCapabilitiesChanged"));
         assertTrue(expectedCapabilities.equals(satelliteCapabilities[0]));
 
-        mSatelliteControllerUT.unregisterForSatelliteCapabilitiesChanged(SUB_ID, callback);
+        mSatelliteControllerUT.unregisterForCapabilitiesChanged(SUB_ID, callback);
         expectedCapabilities = mSatelliteCapabilities;
         sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
         processAllMessages();
@@ -2524,21 +2619,21 @@
                     }
                 };
 
-        int errorCode = mSatelliteControllerUT.registerForSatelliteCapabilitiesChanged(SUB_ID,
+        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.registerForSatelliteCapabilitiesChanged(SUB_ID,
+        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.registerForSatelliteCapabilitiesChanged(SUB_ID,
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
                 callback);
         assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
 
@@ -2549,6 +2644,592 @@
                 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 but
+        // KEY_SATELLITE_ATTACH_SUPPORTED_BOOL is false, verify passing to 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);
+        assertEquals(new ArrayList<>(), plmnListPerCarrier);
+        allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
+                entitlementPlmnList, overlayConfigPlmnList);
+        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
+                eq(entitlementPlmnList), eq(allSatellitePlmnList), any(Message.class));
+
+        // If the PlmnListPerCarrier and the overlay config plmn list are exist and
+        // KEY_SATELLITE_ATTACH_SUPPORTED_BOOL is true verify passing the modem.
+        reset(mMockSatelliteModemInterface);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+
+        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));
+    }
+
+    private void setConfigData(List<String> plmnList) {
+        doReturn(plmnList).when(mMockConfig).getAllSatellitePlmnsForCarrier(anyInt());
+        doReturn(mMockConfig).when(mMockConfigParser).getConfig();
+
+        Map<String, List<Integer>> servicePerPlmn = new HashMap<>();
+        List<List<Integer>> serviceLists = Arrays.asList(
+                Arrays.asList(1),
+                Arrays.asList(3),
+                Arrays.asList(5)
+        );
+        for (int i = 0; i < plmnList.size(); i++) {
+            servicePerPlmn.put(plmnList.get(i), serviceLists.get(i));
+        }
+        doReturn(servicePerPlmn).when(mMockConfig).getSupportedSatelliteServices(anyInt());
+        doReturn(mMockConfig).when(mMockConfigParser).getConfig();
+    }
+
+    @Test
+    public void testUpdateSupportedSatelliteServices() throws Exception {
+        logd("testUpdateSupportedSatelliteServices");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        replaceInstance(SatelliteController.class, "mSatelliteServicesSupportedByCarriers",
+                mSatelliteControllerUT, new HashMap<>());
+        List<Integer> servicesPerPlmn;
+
+        // verify whether an empty list is returned with conditions below
+        // the config data plmn list : empty
+        // the carrier config plmn list : empty
+        setConfigData(new ArrayList<>());
+        setCarrierConfigDataPlmnList(new ArrayList<>());
+        invokeCarrierConfigChanged();
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "31016");
+        assertEquals(new ArrayList<>(), servicesPerPlmn);
+
+        // Verify whether the carrier config plmn list is returned with conditions below
+        // the config data plmn list : empty
+        // the carrier config plmn list : exist with services {{2}, {1, 3}, {2}}
+        setConfigData(new ArrayList<>());
+        setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104"));
+        invokeCarrierConfigChanged();
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101");
+        assertEquals(Arrays.asList(2).stream().sorted().toList(),
+                servicesPerPlmn.stream().sorted().toList());
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00102");
+        assertEquals(Arrays.asList(1, 3).stream().sorted().toList(),
+                servicesPerPlmn.stream().sorted().toList());
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00104");
+        assertEquals(Arrays.asList(2).stream().sorted().toList(),
+                servicesPerPlmn.stream().sorted().toList());
+
+        // Verify whether the carrier config plmn list is returned with conditions below
+        // the config data plmn list : exist with services {{1}, {3}, {5}}
+        // the carrier config plmn list : exist with services {{2}, {1, 3}, {2}}
+        setConfigData(Arrays.asList("00101", "00102", "31024"));
+        setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104"));
+        invokeCarrierConfigChanged();
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101");
+        assertEquals(Arrays.asList(1).stream().sorted().toList(),
+                servicesPerPlmn.stream().sorted().toList());
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00102");
+        assertEquals(Arrays.asList(3).stream().sorted().toList(),
+                servicesPerPlmn.stream().sorted().toList());
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00104");
+        assertEquals(new ArrayList<>(), servicesPerPlmn.stream().sorted().toList());
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "31024");
+        assertEquals(Arrays.asList(5).stream().sorted().toList(),
+                servicesPerPlmn.stream().sorted().toList());
+    }
+    private void setEntitlementPlmnList(List<String> plmnList) throws Exception {
+        SparseArray<List<String>> entitlementPlmnListPerCarrier = new SparseArray<>();
+        if (!plmnList.isEmpty()) {
+            entitlementPlmnListPerCarrier.clear();
+            entitlementPlmnListPerCarrier.put(SUB_ID, plmnList);
+        }
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                mSatelliteControllerUT, entitlementPlmnListPerCarrier);
+    }
+
+    private void setConfigDataPlmnList(List<String> plmnList) {
+        doReturn(plmnList).when(mMockConfig).getAllSatellitePlmnsForCarrier(anyInt());
+        doReturn(mMockConfig).when(mMockConfigParser).getConfig();
+    }
+
+    private void setCarrierConfigDataPlmnList(List<String> plmnList) {
+        if (!plmnList.isEmpty()) {
+            mCarrierConfigBundle.putBoolean(
+                    CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                    true);
+            PersistableBundle carrierSupportedSatelliteServicesPerProvider =
+                    new PersistableBundle();
+            List<String> carrierConfigPlmnList = plmnList;
+            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);
+        } else {
+            mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                            .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                    new PersistableBundle());
+        }
+    }
+
+    private void invokeCarrierConfigChanged() {
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+    }
+
+    @Test
+    public void testUpdatePlmnListPerCarrier() throws Exception {
+        logd("testUpdatePlmnListPerCarrier");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> plmnListPerCarrier;
+
+        // verify whether an empty list is returned with conditions below
+        // the entitlement plmn list : empty
+        // the config data plmn list : empty
+        // the carrier config plmn list : empty
+        setEntitlementPlmnList(new ArrayList<>());
+        setConfigDataPlmnList(new ArrayList<>());
+        setCarrierConfigDataPlmnList(new ArrayList<>());
+        invokeCarrierConfigChanged();
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
+        assertEquals(new ArrayList<>(), plmnListPerCarrier.stream().sorted().toList());
+
+        // Verify whether the carrier config plmn list is returned with conditions below
+        // the entitlement plmn list : empty
+        // the config data plmn list : empty
+        // the carrier config plmn list : exist
+        setEntitlementPlmnList(new ArrayList<>());
+        setConfigDataPlmnList(new ArrayList<>());
+        setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104"));
+        invokeCarrierConfigChanged();
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
+        assertEquals(Arrays.asList("00101", "00102", "00104").stream().sorted().toList(),
+                plmnListPerCarrier.stream().sorted().toList());
+
+        // Verify whether config data plmn list is returned with conditions below
+        // the entitlement plmn list : empty
+        // the config data plmn list : exist
+        // the carrier config plmn list : exist
+        setEntitlementPlmnList(new ArrayList<>());
+        setConfigDataPlmnList(Arrays.asList("11111", "22222", "33333"));
+        setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104"));
+        invokeCarrierConfigChanged();
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
+        assertEquals(Arrays.asList("11111", "22222", "33333").stream().sorted().toList(),
+                plmnListPerCarrier.stream().sorted().toList());
+
+        // Verify whether the entitlement plmn list is returned with conditions below
+        // the entitlement plmn list : exist
+        // the config data plmn list : exist
+        // the carrier config plmn list : exist
+        setEntitlementPlmnList(Arrays.asList("99090", "88080", "77070"));
+        setConfigDataPlmnList(Arrays.asList("11111", "22222", "33333"));
+        setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104"));
+        invokeCarrierConfigChanged();
+        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
+        assertEquals(Arrays.asList("99090", "88080", "77070").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_RESULT_RADIO_NOT_AVAILABLE);
@@ -2614,7 +3295,8 @@
                 ? 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));
@@ -2887,7 +3569,7 @@
             try {
                 if (!mSatelliteAllowedSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
                     loge("Timeout to receive "
-                            + "requestIsSatelliteCommunicationAllowedForCurrentLocation()"
+                            + "requestIsCommunicationAllowedForCurrentLocation()"
                             + " callback");
                     return false;
                 }
@@ -3016,6 +3698,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);
@@ -3112,13 +3804,45 @@
                 throw new AssertionError();
             }
         } else {
-            ServiceSpecificException ex = assertThrows(ServiceSpecificException.class,
+            RemoteException ex = assertThrows(RemoteException.class,
                     () -> mSatelliteControllerUT.registerForNtnSignalStrengthChanged(subId,
                             callback));
-            assertEquals(expectedError, ex.errorCode);
+            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);
     }
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 589f32b..d12828a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
@@ -23,6 +23,8 @@
 
 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;
@@ -38,8 +40,8 @@
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Looper;
+import android.os.OutcomeReceiver;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
 import android.telecom.Connection;
 import android.telephony.BinderCacheManager;
 import android.telephony.ServiceState;
@@ -184,13 +186,22 @@
 
     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)) {
@@ -208,13 +219,22 @@
     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));
@@ -224,12 +244,21 @@
 
     @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
@@ -241,6 +270,7 @@
                 DEFAULT_HANDOVER_INTENT_ACTION));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        mTestSatelliteController.isOemEnabledSatelliteSupported = true;
     }
 
     @Test
@@ -255,13 +285,14 @@
 
     @Test
     public void testImsRegistrationStateChangedBeforeTimeout() {
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
-
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
         assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
         when(mPhone.isImsRegistered()).thenReturn(true);
         mTestImsManager.sendImsRegistrationStateChangedEvent(0, true);
@@ -285,8 +316,16 @@
         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();
 
@@ -324,10 +363,18 @@
         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();
 
@@ -342,23 +389,31 @@
 
     @Test
     public void testEmergencyCallRedialBeforeTimeout() {
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
+        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        processAllMessages();
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
         assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
 
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        // Move Location service to emergency mode
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                mTestConnection.getTelecomCallId(), Connection.STATE_DIALING);
         processAllMessages();
+        assertNotNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
-        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
-        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
-        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
-        assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
-
-        // Wait for the timeout to expires
+        // 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();
 
@@ -418,16 +473,23 @@
 
     @Test
     public void testSatelliteNotAllowedInCurrentLocation() {
-        mTestSatelliteController.setIsSatelliteCommunicationAllowed(false);
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
 
-        /**
-         * We should have registered for the state change events and 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.
-         */
+        // 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();
+
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
@@ -452,6 +514,24 @@
         assertEquals(0, testSOSMessageRecommender.getCountOfTimerStarted());
     }
 
+    @Test
+    public void testIsSatelliteViaOemAvailable() {
+        Boolean originalIsSatelliteViaOemProvisioned =
+                mTestSatelliteController.mIsSatelliteViaOemProvisioned;
+
+        mTestSatelliteController.mIsSatelliteViaOemProvisioned = null;
+        assertFalse(mTestSOSMessageRecommender.isSatelliteViaOemAvailable());
+
+        mTestSatelliteController.mIsSatelliteViaOemProvisioned = true;
+        assertTrue(mTestSOSMessageRecommender.isSatelliteViaOemAvailable());
+
+        mTestSatelliteController.mIsSatelliteViaOemProvisioned = false;
+        assertFalse(mTestSOSMessageRecommender.isSatelliteViaOemAvailable());
+
+        mTestSatelliteController.mIsSatelliteViaOemProvisioned =
+                originalIsSatelliteViaOemProvisioned;
+    }
+
     private void testStopTrackingCallBeforeTimeout(
             @Connection.ConnectionState int connectionState) {
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
@@ -475,9 +555,10 @@
     private void testCellularServiceStateChangedBeforeTimeout(
             @ServiceState.RegState int availableServiceState,
             @ServiceState.RegState int unavailableServiceState) {
+        mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
-
+        assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertRegisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
@@ -505,7 +586,14 @@
         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();
 
@@ -544,9 +632,10 @@
                 mProvisionStateChangedCallbacks;
         private int mRegisterForSatelliteProvisionStateChangedCalls = 0;
         private int mUnregisterForSatelliteProvisionStateChangedCalls = 0;
-        private boolean mIsSatelliteViaOemProvisioned = 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
@@ -567,7 +656,12 @@
 
         @Override
         public boolean isSatelliteSupportedViaOem() {
-            return true;
+            return isOemEnabledSatelliteSupported;
+        }
+
+        @Override
+        public boolean isSatelliteSupportedViaCarrier() {
+            return isCarrierEnabledSatelliteSupported;
         }
 
         @Override
@@ -593,15 +687,6 @@
         }
 
         @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_RESULT_SUCCESS, bundle);
-        }
-
-        @Override
         public boolean isSatelliteConnectedViaCarrierWithinHysteresisTime() {
             return mIsSatelliteConnectedViaCarrierWithinHysteresisTime;
         }
@@ -611,10 +696,6 @@
             return INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
         }
 
-        public void setIsSatelliteCommunicationAllowed(boolean allowed) {
-            mIsSatelliteCommunicationAllowed = allowed;
-        }
-
         public void setSatelliteConnectedViaCarrierWithinHysteresisTime(
                 boolean connectedViaCarrier) {
             mIsSatelliteConnectedViaCarrierWithinHysteresisTime = connectedViaCarrier;
@@ -717,6 +798,8 @@
     }
 
     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);
 
@@ -741,6 +824,14 @@
             return mSmsAppComponent;
         }
 
+        @Override
+        protected void requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                @NonNull OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback) {
+            logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: callback="
+                    + callback);
+            isSatelliteAllowedCallback = callback;
+        }
+
         public boolean isTimerStarted() {
             return hasMessages(EVENT_TIME_OUT);
         }
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 50c6c24..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,6 +18,7 @@
 
 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;
@@ -38,7 +39,7 @@
 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;
@@ -76,7 +77,7 @@
 
     private TestSatelliteModemInterface mSatelliteModemInterface;
     private TestSatelliteSessionController mTestSatelliteSessionController;
-    private TestSatelliteStateCallback mTestSatelliteStateCallback;
+    private TestSatelliteModemStateCallback mTestSatelliteModemStateCallback;
 
     @Mock private SatelliteController mMockSatelliteController;
     @Mock private DatagramReceiver mMockDatagramReceiver;
@@ -107,11 +108,11 @@
                 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
@@ -176,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());
 
@@ -186,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());
 
@@ -196,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());
 
@@ -206,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());
@@ -218,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());
@@ -230,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());
@@ -242,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());
@@ -255,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());
@@ -267,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());
@@ -280,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());
@@ -293,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());
@@ -305,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());
@@ -316,7 +317,7 @@
         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());
@@ -327,7 +328,7 @@
         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());
@@ -340,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());
@@ -352,7 +353,7 @@
         processAllMessages();
 
         // SatelliteSessionController should stay at TRANSFERRING state.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
@@ -364,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());
 
@@ -375,7 +376,7 @@
         processAllMessages();
 
         // SatelliteSessionController should stay at TRANSFERRING state.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
@@ -387,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());
 
@@ -397,7 +398,7 @@
 
         // 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());
     }
@@ -419,8 +420,8 @@
 
         // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
         // is powered on.
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
         assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
@@ -430,7 +431,7 @@
         moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
         processAllMessages();
         // SatelliteSessionController should stay at NOT_CONNECTED state.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
 
         setupDatagramTransferringState(true);
@@ -441,7 +442,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());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_OFF);
@@ -452,8 +453,8 @@
         processAllMessages();
 
         // SatelliteSessionController should move to NOT_CONNECTED state after radio is turned on.
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
         assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
@@ -466,7 +467,7 @@
         processAllMessages();
 
         // The datagram sending event should be ignored.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
 
         // Satellite modem is connected to a satellite network.
@@ -476,7 +477,7 @@
 
         // SatelliteSessionController should move to CONNECTED state
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
@@ -489,7 +490,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.isNbIotInactivityTimerStarted());
@@ -504,7 +505,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
@@ -519,7 +520,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.isNbIotInactivityTimerStarted());
@@ -534,7 +535,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
@@ -549,7 +550,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.isNbIotInactivityTimerStarted());
@@ -563,7 +564,36 @@
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        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());
@@ -576,7 +606,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to IDLE state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
@@ -591,7 +621,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to NOT_CONNECTED state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
         assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
@@ -605,7 +635,7 @@
 
         // SatelliteSessionController should move to CONNECTED state
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
@@ -617,8 +647,8 @@
         processAllMessages();
 
         // SatelliteSessionController should move to NOT_CONNECTED state
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
         assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
@@ -631,7 +661,7 @@
 
         // SatelliteSessionController should move to CONNECTED state
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
@@ -643,7 +673,7 @@
 
         // 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());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_OFF);
@@ -655,8 +685,8 @@
 
         // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
         // is powered on.
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
         assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
@@ -669,7 +699,7 @@
 
         // SatelliteSessionController should move to CONNECTED state
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
 
         // Wait for timeout
@@ -677,7 +707,7 @@
         processAllMessages();
 
         // SatelliteSessionController should move to IDLE state.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
 
@@ -692,7 +722,7 @@
 
         // SatelliteSessionController should stay at IDLE state because it failed to disable
         // cellular scanning.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
 
         mSatelliteModemInterface.setErrorCode(SatelliteManager.SATELLITE_RESULT_SUCCESS);
@@ -703,7 +733,7 @@
 
         // SatelliteSessionController should move to POWER_OFF
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
 
         // Power on the modem.
@@ -712,8 +742,8 @@
 
         // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
         // is powered on.
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
         assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
 
         moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
@@ -721,7 +751,7 @@
 
         // SatelliteSessionController should move to IDLE state because NB-IOT inactivity timer has
         // timed out.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
 
@@ -731,7 +761,7 @@
 
         // SatelliteSessionController should move to POWER_OFF
         assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
 
         // Power on the modem.
@@ -740,8 +770,8 @@
 
         // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
         // is powered on.
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        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.
@@ -752,7 +782,7 @@
         processAllMessages();
 
         // SatelliteSessionController should stay at NOT_CONNECTED state because.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
         assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
 
         // Transferring datagram failed because satellite failed to connect to a satellite network.
@@ -771,7 +801,7 @@
 
         // SatelliteSessionController should move to IDLE state because NB-IOT inactivity timer has
         // timed out.
-        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
     }
@@ -850,7 +880,7 @@
         }
     }
 
-    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);
@@ -885,7 +915,7 @@
     }
 
     private static void assertSuccessfulModemStateChangedCallback(
-            TestSatelliteStateCallback callback,
+            TestSatelliteModemStateCallback callback,
             @SatelliteManager.SatelliteModemState int expectedModemState) {
         boolean successful = callback.waitUntilResult();
         assertTrue(successful);
@@ -893,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..aba8164
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/CellularIdentifierDisclosureNotifierTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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 static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.telephony.CellularIdentifierDisclosure;
+
+import com.android.internal.telephony.TestExecutorService;
+import com.android.internal.telephony.metrics.CellularSecurityTransparencyStats;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
+import org.junit.Assert;
+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 CellularSecurityTransparencyStats mStats;
+    private TestExecutorService mExecutor;
+    private SubscriptionManagerService mSubscriptionManagerService;
+    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);
+        mStats = mock(CellularSecurityTransparencyStats.class);
+        mExecutor = new TestExecutorService();
+        mSubscriptionManagerService = mock(SubscriptionManagerService.class);
+        mContext = mock(Context.class);
+        mInOrder = inOrder(mSafetySource);
+    }
+
+    @Test
+    public void testInitializeDisabled() {
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+
+        assertFalse(notifier.isEnabled());
+        verify(mSafetySource, never()).setIdentifierDisclosureIssueEnabled(any(), anyBoolean());
+    }
+
+    @Test
+    public void testDisableAddDisclosureNop() {
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+
+        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() {
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+
+        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() {
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+        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() {
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+        notifier.enable(mContext);
+
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+
+        assertEquals(1, notifier.getCurrentDisclosureCount(SUB_ID_1));
+        Assert.assertEquals(notifier.getFirstOpen(SUB_ID_1), notifier.getCurrentEnd(SUB_ID_1));
+        mInOrder.verify(mSafetySource, times(1))
+                .setIdentifierDisclosure(any(), eq(SUB_ID_1), eq(1), any(), any());
+    }
+
+    @Test
+    public void testMultipleDisclosuresTimeWindows() {
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+        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() {
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+
+        // 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
+        mExecutor.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() {
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+
+        // 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() {
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+
+        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));
+    }
+
+    @Test
+    public void testLogDisclsoure() {
+        String mcc = "100";
+        String mnc = "200";
+
+        CellularIdentifierDisclosureNotifier notifier = getNotifier();
+        SubscriptionInfoInternal subInfoMock = mock(SubscriptionInfoInternal.class);
+        when(mSubscriptionManagerService.getSubscriptionInfoInternal(SUB_ID_1)).thenReturn(
+                subInfoMock);
+        when(subInfoMock.getMcc()).thenReturn(mcc);
+        when(subInfoMock.getMnc()).thenReturn(mnc);
+
+        notifier.addDisclosure(mContext, SUB_ID_1, mDislosure);
+
+        verify(mStats, times(1)).logIdentifierDisclosure(mDislosure, mcc, mnc,
+                mDislosure.isEmergency());
+    }
+
+    private CellularIdentifierDisclosureNotifier getNotifier() {
+        return new CellularIdentifierDisclosureNotifier(mExecutor, 15, TimeUnit.MINUTES,
+                mSafetySource, mSubscriptionManagerService, mStats);
+    }
+}
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/NullCipherNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
new file mode 100644
index 0000000..0bb7b76
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
@@ -0,0 +1,377 @@
+/*
+ * 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.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.eq;
+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.SecurityAlgorithmUpdate;
+
+import com.android.internal.telephony.TestExecutorService;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+public class NullCipherNotifierTest {
+
+    private static final int SUB_ID = 3425;
+    private static final List<Integer> NON_TRANSPORT_LAYER_EVENTS =
+            List.of(SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP_SOS,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_RTP_SOS,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_SIP_SOS,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_VONR_RTP_SOS);
+    private static final List<Integer> TRANSPORT_LAYER_EVENTS =
+            List.of(SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_GSM,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_GPRS,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_CS_SIGNALLING_3G,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_PS_SIGNALLING_3G,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_LTE,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_LTE,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_NAS_SIGNALLING_5G,
+                    SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G);
+    private static final List<Integer> NULL_CIPHERS =
+            List.of(SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A50,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA0,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA0,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA0,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA0,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_IMS_NULL,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SIP_NULL,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_NULL,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_OTHER);
+    private static final List<Integer> NON_NULL_CIPHERS =
+            List.of(SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A51,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A52,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A53,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_A54,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA2,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA3,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA4,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_GEA5,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_UEA2,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA3,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA2,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_NEA3,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AES_GCM,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AES_GMAC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AES_CBC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_DES_EDE3_CBC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AES_EDE3_CBC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_MD5_96,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_RTP,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_AES_COUNTER,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_AES_F8,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_SRTP_HMAC_SHA1,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_ENCR_AES_GCM_16,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_ENCR_AES_CBC,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+                    SecurityAlgorithmUpdate.SECURITY_ALGORITHM_ORYX);
+
+    private CellularNetworkSecuritySafetySource mSafetySource;
+    private Context mContext;
+    private TestExecutorService mExecutor = new TestExecutorService();
+
+    @Before
+    public void setUp() {
+        mSafetySource = mock(CellularNetworkSecuritySafetySource.class);
+    }
+
+    @Test
+    public void initializeNotifier_notifierAndSafetySourceDisabled() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        assertThat(notifier.isEnabled()).isFalse();
+        verify(mSafetySource, never()).setNullCipherIssueEnabled(any(), anyBoolean());
+    }
+
+    @Test
+    public void enable_enablesSafetySource() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        notifier.enable(mContext);
+
+        assertThat(notifier.isEnabled()).isTrue();
+        verify(mSafetySource, times(1)).setNullCipherIssueEnabled(eq(mContext), eq(true));
+    }
+
+    @Test
+    public void disable_disablesSafetySource() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
+
+        notifier.disable(mContext);
+
+        assertThat(notifier.isEnabled()).isFalse();
+        verify(mSafetySource, times(1)).setNullCipherIssueEnabled(eq(mContext), eq(false));
+    }
+
+    @Test
+    public void onSecurityAlgorithmUpdate_enabled_unprotectedEmergency_noSafetySourceUpdate() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                SUB_ID,
+                new SecurityAlgorithmUpdate(
+                        SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                        /* isUnprotectedEmergency= */ true));
+
+        verify(mSafetySource, never()).setNullCipherState(any(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void onSecurityAlgorithmUpdate_enabled_nonTransportLayerEvent_noSafetySourceUpdate() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : NON_TRANSPORT_LAYER_EVENTS) {
+            clearInvocations(mSafetySource);
+            notifier.onSecurityAlgorithmUpdate(
+                    mContext,
+                    SUB_ID,
+                    new SecurityAlgorithmUpdate(
+                            connectionEvent,
+                            SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                            SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                            /* isUnprotectedEmergency= */ false));
+
+            verify(mSafetySource, never().description("Connection event: " + connectionEvent))
+                    .setNullCipherState(any(), anyInt(), anyInt());
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_encryptionNullCipher_notifyNonEncrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int encryptionAlgorithm : NULL_CIPHERS) {
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                encryptionAlgorithm,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Encryption algorithm: " + encryptionAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_integrityNullCipher_notifyNonEncrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int integrityAlgorithm : NULL_CIPHERS) {
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                                integrityAlgorithm,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Integrity algorithm: " + integrityAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_encryptionNonNullCipher_encrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int encryptionAlgorithm : NON_NULL_CIPHERS) {
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                encryptionAlgorithm,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Encryption algorithm: " + encryptionAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_integrityNonNullCipher_encrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int integrityAlgorithm : NON_NULL_CIPHERS) {
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                                integrityAlgorithm,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Integrity algorithm: " + integrityAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_encryptionNonNullCipher_notifyEncrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int encryptionAlgorithm : NON_NULL_CIPHERS) {
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                NULL_CIPHERS.get(0),
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                                /* isUnprotectedEmergency= */ false));
+
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                encryptionAlgorithm,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Encryption algorithm: " + encryptionAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_NOTIFY_ENCRYPTED));
+            }
+        }
+    }
+
+    @Test
+    public void onUpdate_enabled_transportLayerEvent_integrityNonNullCipher_notifyEncrypted() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
+
+        for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
+            for (int integrityAlgorithm : NON_NULL_CIPHERS) {
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                                NULL_CIPHERS.get(0),
+                                /* isUnprotectedEmergency= */ false));
+
+                clearInvocations(mSafetySource);
+                notifier.onSecurityAlgorithmUpdate(
+                        mContext,
+                        SUB_ID,
+                        new SecurityAlgorithmUpdate(
+                                connectionEvent,
+                                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                                integrityAlgorithm,
+                                /* isUnprotectedEmergency= */ false));
+
+                verify(
+                        mSafetySource,
+                        times(1).description(
+                                "Connection event: " + connectionEvent
+                                        + " Integrity algorithm: " + integrityAlgorithm))
+                        .setNullCipherState(
+                                eq(mContext),
+                                eq(SUB_ID),
+                                eq(NULL_CIPHER_STATE_NOTIFY_ENCRYPTED));
+            }
+        }
+    }
+}
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 b42e6d6..7339e42 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
@@ -124,12 +124,23 @@
     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)
@@ -199,6 +210,10 @@
                             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 =
@@ -270,6 +285,10 @@
                             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;
@@ -429,6 +448,8 @@
         doReturn(1).when(mUiccController).convertToPublicCardId(eq(FAKE_ICCID1));
         doReturn(2).when(mUiccController).convertToPublicCardId(eq(FAKE_ICCID2));
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        when(mFeatureFlags.dataOnlyCellularService()).thenReturn(true);
+        when(mFeatureFlags.supportPsimToEsimConversion()).thenReturn(true);
         mDatabaseManagerUT = new SubscriptionDatabaseManager(mContext, Looper.myLooper(),
                 mFeatureFlags, mSubscriptionDatabaseManagerCallback);
         logd("SubscriptionDatabaseManagerTest -Setup!");
@@ -699,7 +720,10 @@
         assertThrows(IllegalArgumentException.class,
                 () -> mDatabaseManagerUT.setNumber(1, FAKE_PHONE_NUMBER2));
 
-        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        // Prevent the carrier number from overriding the display number
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(
+                new SubscriptionInfoInternal.Builder(FAKE_SUBSCRIPTION_INFO1)
+                        .setNumberFromCarrier("").build());
         mDatabaseManagerUT.setNumber(subInfo.getSubscriptionId(), FAKE_PHONE_NUMBER2);
         processAllMessages();
 
@@ -2011,6 +2035,36 @@
     }
 
     @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(
@@ -2174,4 +2228,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 2505f32..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 =
@@ -108,6 +113,15 @@
                             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 =
@@ -134,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);
@@ -224,6 +243,15 @@
                 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
@@ -235,6 +263,7 @@
 
     @Test
     public void testConvertToSubscriptionInfo() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE);
         SubscriptionInfo subInfo = mSubInfo.toSubscriptionInfo();
 
         assertThat(subInfo.getSubscriptionId()).isEqualTo(1);
@@ -287,6 +316,9 @@
                 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 eecaf90..defa730 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;
@@ -113,6 +114,7 @@
 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;
@@ -232,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!");
     }
 
@@ -346,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
@@ -407,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);
@@ -444,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);
@@ -481,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.
@@ -538,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);
@@ -770,6 +848,10 @@
 
         // 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, true);
@@ -799,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);
@@ -893,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());
 
@@ -914,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);
@@ -1161,6 +1257,7 @@
             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);
@@ -1222,6 +1319,7 @@
     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);
@@ -1250,9 +1348,17 @@
         // Test getActiveSubIdList, System
         assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false/*visible only*/))
                 .isEqualTo(new int[]{subId1, subId2});
-        // Test get getActiveSubInfoCount
+        // 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);
@@ -1272,11 +1378,21 @@
         assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForSimSlotIndex(
                 1, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
                 .isEqualTo(subId2);
-        // Test getActiveSubscriptionInfoList
+        // 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())
@@ -1339,6 +1455,7 @@
     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);
@@ -1367,9 +1484,17 @@
         // Test getActiveSubIdList, System
         assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false/*visible only*/))
                 .isEqualTo(new int[]{subId1, subId2});
-        // Test get getActiveSubInfoCount
+        // 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);
@@ -1389,11 +1514,21 @@
         assertThat(mSubscriptionManagerServiceUT.getActiveSubscriptionInfoForSimSlotIndex(
                 1, CALLING_PACKAGE, CALLING_FEATURE).getSubscriptionId())
                 .isEqualTo(subId2);
-        // Test getActiveSubscriptionInfoList
+        // 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())
@@ -1476,7 +1611,9 @@
 
     @Test
     public void testSetDisplayNumber() {
-        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+        insertSubscription(new SubscriptionInfoInternal.Builder(FAKE_SUBSCRIPTION_INFO1)
+                .setNumberFromCarrier("")
+                .build());
 
         // Should fail without MODIFY_PHONE_STATE
         assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT
@@ -1522,7 +1659,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);
@@ -2028,6 +2170,39 @@
     }
 
     @Test
+    public void testGetNumberWithCarrierNumber() {
+        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+
+        // Should fail without MODIFY_PHONE_STATE
+        assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT
+                .setDisplayNumber(FAKE_PHONE_NUMBER2, 1));
+
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+
+        mSubscriptionManagerServiceUT.setDisplayNumber(FAKE_PHONE_NUMBER2, 1);
+        processAllMessages();
+        verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(1));
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(1);
+        assertThat(subInfo).isNotNull();
+        assertThat(subInfo.getNumber()).isEqualTo(FAKE_PHONE_NUMBER1);
+        Mockito.clearInvocations(mMockedSubscriptionManagerServiceCallback);
+
+        setCarrierPrivilegesForSubId(true, 1);
+        mSubscriptionManagerServiceUT.setPhoneNumber(1,
+                SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, "",
+                CALLING_PACKAGE, CALLING_FEATURE);
+        processAllMessages();
+        verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(1));
+        setCarrierPrivilegesForSubId(false, 1);
+
+        subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(1);
+        assertThat(subInfo).isNotNull();
+        assertThat(subInfo.getNumber()).isEqualTo(FAKE_PHONE_NUMBER2);
+    }
+
+    @Test
     public void testGetNonAccessibleFields() throws Exception {
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
 
@@ -2271,6 +2446,7 @@
     }
 
     @Test
+    @DisableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
     public void testGetPhoneNumber() {
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         testSetPhoneNumber();
@@ -2874,4 +3050,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/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 1d320a3..671f273 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
@@ -428,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());
 
@@ -451,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 d5b6ccb..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;
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;